---
title: Partner-Branded Delegation
description: Let your users authorize AI agent spending with a passkey-signed mandate — one deep link, on a page wearing your brand.
---

Before an AI agent can spend on a user's behalf, the user has to say *how much, where, and until when*. That authorization is a **delegation** (an IntentMandate): a spending scope — categories, per-transaction and daily limits, optional merchant whitelist — signed on the user's own device with a passkey and issued as a verifiable credential.

**Partner-Branded Delegation** is the hosted page where that happens: your app opens one deep link, the user reviews the scope on a page wearing your wordmark and accent color, confirms with Touch ID / their device passkey, and your app gets signaled the moment the mandate exists.

- Wears **your wordmark and accent color** (same dashboard settings as [Checkout](/docs/partner-program/checkout))
- Opens as a **desktop popup** or **mobile full-page redirect**
- You can **suggest** the scope via query params — the user always reviews, can edit anything, and signs exactly what's on screen

<Callout type="info">
**Prefill is a suggestion, never an instruction.** Whatever limits you pass in the URL, the user sees them, can change them, and the passkey signs the final on-screen scope. Server-side validation is authoritative. You cannot create or widen a mandate on a user's behalf.
</Callout>

---

## The flow

```
┌────────────────┐                                          ┌────────────────┐
│                │  1. user clicks "Authorize agent"        │                │
│  Your app      │─────────────────────────────────────────▶│  popup /       │
│                │                                          │  redirect      │
└────────┬───────┘                                          └────────┬───────┘
         │                                                           │
         │                                2. Delegation form loads
         │                                (your brand, your suggested limits)
         │                                                           │
         │                                3. user reviews, edits, signs
         │                                   with device passkey
         │                                                           │
         │     4. 'agnic:delegation_created' postMessage / redirect  │
         │◀──────────────────────────────────────────────────────────┘
         │
         ▼
  agents can now spend within the signed scope
```

| Party      | Owns                                                                 |
|------------|-----------------------------------------------------------------------|
| **You**    | Open the deep link (optionally with suggested limits); listen for completion |
| **Agnic**  | Renders the form; passkey ceremony; issues + stores the mandate       |

---

## Step 1 — Brand the page

Same dashboard fields as Checkout and Add Card — **Display name** and **Accent color** under your OAuth client's **Checkout settings** at [app.agnic.ai](https://app.agnic.ai).

---

## Step 2 — Open the deep link

### URL format

```
https://app.agnic.ai/partner/delegations/new
  ?client_id={YOUR_CLIENT_ID}
  &return_url={URL_ENCODED_RETURN}
  &categories=food_beverage,merchandise      # optional prefill
  &max_per_tx=50.00                          # optional prefill
  &daily_limit=200.00                        # optional prefill
  &currency=USD                              # optional prefill
  &valid_days=30                             # optional prefill
  &merchant_whitelist=merchant_maple_grounds # optional prefill
```

| Param                | Required | Notes                                                                                     |
|----------------------|----------|--------------------------------------------------------------------------------------------|
| `client_id`          | ✓        | Your `app_…` client id. Must be approved and not revoked.                                 |
| `return_url`         | ✓        | Absolute URL, URL-encoded. **Must match one of the client's registered `redirect_uris`.** |
| `categories`         | ✗        | Comma-separated: `payment`, `general`, `food_beverage`, `alcohol`, `transportation`, `merchandise`, `subscription` |
| `max_per_tx`         | ✗        | Decimal string, e.g. `50.00`                                                              |
| `daily_limit`        | ✗        | Decimal string; must be ≥ `max_per_tx` to pass form validation                            |
| `currency`           | ✗        | `USD`, `CAD`, `GBP`, or `EUR`                                                             |
| `valid_days`         | ✗        | Integer 1–365                                                                             |
| `merchant_whitelist` | ✗        | Comma-separated Agnic merchant IDs                                                        |

Malformed prefill values are silently dropped to sensible defaults — a bad deep link never blocks the user.

### Reference implementation

```tsx
const handleAuthorizeAgent = () => {
  const returnUrl = `${window.location.origin}/your-page`;
  const clientId = process.env.NEXT_PUBLIC_AGNIC_OAUTH_CLIENT_ID!;
  const params = new URLSearchParams({
    client_id: clientId,
    return_url: returnUrl,
    categories: "food_beverage,merchandise",
    max_per_tx: "50.00",
    daily_limit: "200.00",
    currency: "USD",
  });
  const url = `https://app.agnic.ai/partner/delegations/new?${params}`;

  if (window.innerWidth < 640) {
    window.location.href = url;
    return;
  }
  const width = 520;  // the scope form is taller than Checkout — give it room
  const height = 860;
  const left = Math.max(0, Math.round(window.screenX + (window.outerWidth - width) / 2));
  const top = Math.max(0, Math.round(window.screenY + (window.outerHeight - height) / 2));
  window.open(url, "agnic-delegation", `width=${width},height=${height},left=${left},top=${top},popup=yes`);
};
```

The popup is a top-level window, so the passkey prompt (Touch ID / Windows Hello / phone) works exactly as it does on any normal page.

---

## Step 3 — Handle the return

### Desktop: `postMessage`

```tsx
useEffect(() => {
  const onMessage = (ev: MessageEvent) => {
    if (ev.origin !== "https://app.agnic.ai") return;
    if (!ev.data || typeof ev.data !== "object") return;

    if (ev.data.type === "agnic:delegation_created") {
      toast.success("Agent spending authorized.");
      refreshDelegationStatus();
    }
    if (ev.data.type === "agnic:delegation_cancelled") {
      // user closed without signing — no-op
    }
  };
  window.addEventListener("message", onMessage);
  return () => window.removeEventListener("message", onMessage);
}, []);
```

| `type`                        | Payload | When                                        |
|-------------------------------|---------|---------------------------------------------|
| `agnic:delegation_created`    | —       | Mandate signed and issued                   |
| `agnic:delegation_cancelled`  | —       | User closed the popup before signing        |

The message is a success signal only — the credential itself is never exposed to your app.

### Mobile: `?delegation=success` on the return URL

| Param        | Values                    | Notes                                    |
|--------------|---------------------------|-------------------------------------------|
| `delegation` | `success` \| `cancelled`  | Appended to your `return_url` on mobile  |

Handle it the same way as the [Checkout mobile return](/docs/partner-program/checkout#mobile-topupsuccess-on-the-return-url), with `delegation` in place of `topup`.

---

## Security model — what this means for you

| Concern                                   | How Delegation handles it                                                                       |
|-------------------------------------------|---------------------------------------------------------------------------------------------------|
| Partner inflating limits                  | Prefill is display-only. The passkey signs the scope the user sees; the server validates limits.   |
| Arbitrary redirect (open-redirect abuse)  | `return_url` must match a `redirect_uri` on your OAuth client. Rejected otherwise.                 |
| `postMessage` spoofing from another tab   | **You** validate `ev.origin === "https://app.agnic.ai"` in your listener.                          |
| Credential leakage                        | The signed mandate (SD-JWT) never leaves Agnic — your app only gets a success signal.              |
| Who the mandate binds to                  | The user's own passkey, on their own device, on their own Agnic account. Strictly user-scoped.     |

---

## Testing

### Manual smoke-test checklist

- Desktop Chrome: popup opens 520×860, form shows your prefilled limits, all fields editable
- Sign with passkey → success screen → `agnic:delegation_created` fires → popup closes ~2 s later
- First-time user (no passkey yet): registration prompt then signing prompt — two ceremonies is expected
- Malformed prefill (`max_per_tx=abc`, unknown category) → form falls back to defaults, no error
- Click ✕ → `agnic:delegation_cancelled` fires
- Mobile (viewport < 640 px): full-page redirect; success returns `return_url?delegation=success`
- `return_url` not registered → error page, no form shown

---

## FAQ

**What if the user already has a delegation?**
The form shows an "active mandate" notice; signing a new one supersedes the old. There is one active mandate per user, not per partner.

**Can I read the mandate's limits afterwards?**
No. The mandate belongs to the user. Your app learns only that one exists (success signal). Agents you run through Agnic's APIs will simply succeed or hit approval prompts based on the user's actual scope.

**Why a passkey and not just a confirmation button?**
The authenticator signs the exact scope shown — a cryptographic record of what the user authorized, usable in disputes. A button click proves nothing later.

**Can this run in an iframe?**
No — same policy as [Add Card](/docs/partner-program/add-card): the page refuses framing, and passkey ceremonies are unreliable in cross-origin iframes anyway. Use the popup.

---

<Cards>
  <Card title="Partner-Branded Add Card" href="/docs/partner-program/add-card">
    Get a card on file first — delegations spend on the card rail
  </Card>
  <Card title="Partner-Branded Checkout" href="/docs/partner-program/checkout">
    Same pattern for topping up credit
  </Card>
  <Card title="OAuth 2.0 integration" href="/docs/authentication/oauth2">
    The same `client_id` powers Sign in with Agnic and hosted flows
  </Card>
  <Card title="Manage OAuth clients" href="https://app.agnic.ai/oauth-clients">
    Create clients, register redirect URIs, set branding
  </Card>
</Cards>
