---
title: Agent Card
description: Issue a virtual card an agent can spend with, governed by the same mandate layer as Pix. Every authorization is checked server-side; freeze or cancel instantly.
---

<MetaStrip items={[
  { label: "TIME", value: "~15 min" },
  { label: "CAPABILITY", value: (<><ServerChip name="codespar_issue" accent /><ServerChip name="Guardrails" /></>) },
  { label: "SIDE", value: (<><ServerChip name="Buy-side" accent /><span style={{ color: "var(--color-fd-muted-foreground)", fontSize: 12 }}>agent spends</span></>) },
]} />

Give your agent its own card instead of a human's. The card is issued through our card-issuing partner, bound to a mandate, and every authorization is evaluated against policy on our side before the network sees an approve or decline. If the agent misbehaves, you freeze or cancel the card in one call.

## Why an issued card, not a borrowed one

The obvious shortcut is to paste a human's card number into the agent's context. It fails three ways:

- **Form-fill walls.** Checkout pages fight bots with 3DS challenges, CAPTCHAs, and device fingerprinting. An agent typing a human's PAN into a form is exactly the traffic those walls exist to stop.
- **No caps.** A borrowed card carries the human's full limit. There is no way to say "this agent gets R$500 a month and nothing more" at the card level.
- **No kill switch.** If the agent goes wrong, your recourse is calling the bank. With an issued card, `card-control: freeze` takes effect at the next authorization.

An issued card flips the model: the instrument belongs to the agent, the limits belong to the mandate, and the authorization decision happens on infrastructure you govern.

## The flow

<PixFlow
  steps={[
    { label: "Issue a virtual card", tool: "session.issue(\"card-virtual\")", description: "Card created at our card-issuing partner, active immediately, bound to the agent's mandate", iconKey: "card" },
    { label: "Agent spends at any merchant", tool: "card network", description: "The card works anywhere cards work: SaaS checkouts, APIs, marketplaces", iconKey: "qr" },
    { label: "Authorization checked against the mandate", tool: "authorization webhook", description: "Each authorization is evaluated server-side against caps and policy before approve/decline", iconKey: "doc" },
    { label: "Freeze or cancel on demand", tool: "session.issue(\"card-control\")", description: "One call disables the card at the next authorization attempt", iconKey: "search" },
  ]}
/>

This is the card face of the same governance you get on Pix: one mandate layer, two rails. The mandate does not care whether the debit arrives as a Pix initiation or a card authorization; the caps and the audit chain are shared.

## Prerequisites

- A CodeSpar API key (`csk_test_...`) with at least one [project](/docs/concepts/projects)
- Card issuing enabled on your account (contact us to provision a cardholder and a card program; you will reference both by id)

```bash
npm install @codespar/sdk
```

## 1. Issue the virtual card

`codespar_issue` with `action: "card-virtual"` (the default) creates a card that is active immediately. `cardholder_id` and `program_id` come from your issuing setup:

```typescript title="agent-card.ts"
import { CodeSpar } from "@codespar/sdk";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY! });

const session = await codespar.create("agent-card-demo", {
  metadata: { source: "cookbook:agent-card" },
});

const card = await session.issue({
  action: "card-virtual",
  cardholder_id: "<issuer-user-id>",
  program_id: "<issuer-card-program-id>",
});

console.log("Card issued:", card.card_id);
```

The card is now a spend instrument the agent can use at checkout. It is not a transfer: no money moves until a merchant authorizes against it, and no authorization clears without passing the mandate check.

## 2. Check card status

`card-get` reads the card's current state. Useful as a pre-flight before handing the card to a purchase flow:

```typescript
const status = await session.execute("codespar_issue", {
  action: "card-get",
  card_id: card.card_id,
});

console.log(status); // { card_id, state: "active", ... }
```

## 3. Authorizations hit the mandate, not your inbox

When the agent uses the card at a merchant, the issuer calls our authorization webhook in real time. We evaluate the authorization against the agent's mandate: amount caps, merchant policy, velocity. Within budget approves; outside it declines. Every decision lands on the [audit chain](/docs/concepts/audit-chain), same as a governed Pix payment.

You do not integrate anything for this step. It is the point of issuing through the platform instead of holding raw issuer credentials: the policy check is server-side and the agent cannot route around it.

## 4. Freeze, then cancel

Freeze is reversible and takes effect at the next authorization attempt. Cancel is terminal:

```typescript
// Pause the card without destroying it
await session.issue({
  action: "card-control",
  card_id: card.card_id,
  control: "freeze",
  reason: "agent budget review",
});

// Resume
await session.issue({
  action: "card-control",
  card_id: card.card_id,
  control: "unfreeze",
});

// Kill it for good
await session.issue({
  action: "card-control",
  card_id: card.card_id,
  control: "cancel",
  reason: "agent decommissioned",
});
```

The `reason` string is stamped on the control action and shows up in the audit trail, so a freeze six months from now still explains itself.

## MCP variant: ask for the card in plain language

If your agent connects over MCP (`npx -y @codespar/mcp serve` in Claude, Codex, or any MCP client), it can do all of the above conversationally:

> "Issue a virtual card for this agent with a R$500 monthly cap. If anything looks off, freeze it."

The agent calls `codespar_issue` with `card-virtual`, the cap lives in the mandate, and the freeze is one `card-control` call away. Same server-side governance; no SDK code required.

## Variations

### Physical card

Same call, `action: "card-physical"`, plus a `shipping_address`. Useful when the "agent" is a back-office workflow whose spend ends at a real-world counter:

```typescript
await session.issue({
  action: "card-physical",
  cardholder_id: "<issuer-user-id>",
  program_id: "<issuer-card-program-id>",
  shipping_address: {
    line1: "Av. Exemplo 100",
    city: "Rio de Janeiro",
    state: "RJ",
    postal_code: "22000-000",
    country: "BR",
  },
});
```

### One card per agent

Issue one virtual card per agent identity rather than sharing a card across agents. Cards are cheap, mandates are per-agent, and a per-agent card means a freeze isolates exactly one workload instead of pausing your whole fleet.

<Callout title="Current status" type="warn">
Card issuing runs in a partner **staging environment** today; production issuing is in provisioning. The authorization webhook runs in **observe mode** before we flip it to enforce: decisions are computed and logged against the mandate, but the issuer's own limits are the backstop until enforcement is enabled per account. Build against this cookbook now; the flip to enforce changes no API surface.
</Callout>

## Next steps

<NextStepsGrid items={[
  { label: "CONCEPT", title: "codespar_issue", description: "Full args reference: virtual/physical, controls, operator setup.", href: "/docs/concepts/meta-tools/issue" },
  { label: "CONCEPT", title: "Wallets", description: "Spend pools and programmable limits behind the card's mandate.", href: "/docs/concepts/wallets" },
  { label: "CONCEPT", title: "Directed Pay", description: "The mandate model that governs both card and Pix spend.", href: "/docs/concepts/directed-pay" },
  { label: "CONCEPT", title: "Guardrails", description: "Budgets, rate limits, and approvals evaluated before any call dispatches.", href: "/docs/concepts/guardrails" },
]} />
