> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tagada.io/llms.txt
> Use this file to discover all available pages before exploring further.

# API keys & authentication

> The three keys — Partner Key, CRM Key, Processing Key — their scopes, formats, rotation, and blast radius.

# API keys & authentication

The platform's two domains each have their own key. A third key — the **Partner Key** — is the master credential you use to provision both. Three keys, three scopes, three blast radii.

***

## The three keys

| Key                | Format                                                   | Domain / base URL                | Scope                        | Minted by                                |
| ------------------ | -------------------------------------------------------- | -------------------------------- | ---------------------------- | ---------------------------------------- |
| **Partner Key**    | `tp_sk_live_…` / `tp_sk_test_…`                          | provisioning on **both**         | All your merchants & TPAs    | TagadaPay (Partner Hub)                  |
| **CRM Key**        | `sk_crm_live_…` / `sk_crm_test_…` (legacy UUID accepted) | CRM — `/api/public/v1`           | One **merchant** (`acc_xxx`) | `partners.crm.merchants.keys.create()`   |
| **Processing Key** | `tp_sk_live_…` / `tp_sk_test_…`                          | Processing — `/api/tagadapay/v1` | One **TPA** (`tpa_xxx`)      | `partners.processing.tpas.keys.create()` |

<Note>
  **Partner and Processing keys share the `tp_sk_live_…` / `tp_sk_test_…` prefix** — you cannot tell them apart by looking at the string. The scope (**partner** vs **one TPA**) is attached server-side to the key, not encoded in the prefix. Keep your Partner Key clearly labelled in your secret manager; it is the only `tp_sk_…` key that can provision.
</Note>

```
┌──────────────────────────────────────────────────────────────┐
│  PARTNER KEY  tp_sk_live_…   (scope: partner, server-side)     │
│  Can: provision merchants + TPAs, mint CRM/Processing keys,    │
│       submit KYB. Scope: ALL your merchants & TPAs.            │
│  Used by: your provisioning / onboarding pipeline only.        │
└───────────────┬───────────────────────────┬───────────────────┘
                │                            │
 crm.merchants.keys.create(acc)   processing.tpas.keys.create(tpa)
                ▼                            ▼
┌──────────────────────────┐   ┌──────────────────────────────┐
│  CRM KEY  sk_crm_live_…   │   │  PROCESSING KEY  tp_sk_live_… │
│  CRM API for ONE merchant │   │  Charging/reporting for ONE   │
│  (orders, customers, …)   │   │  TPA (charge, refund, …)      │
│  Scope: one acc_xxx       │   │  Scope: one tpa_xxx           │
└──────────────────────────┘   └──────────────────────────────┘
```

<Warning>
  **Never use the Partner Key in your runtime path.** Mint a CRM Key and/or a Processing Key per merchant on creation, store them next to the merchant's record in your secret manager, and use only those thereafter. A leaked scoped key exposes one merchant — never your whole partnership.
</Warning>

<Note>
  **API keys ≠ Ops roles.** Partner / CRM / Processing keys authenticate the Node SDK and REST. They do **not** carry Clerk `tgdRoles` and do **not** open the Ops dashboard. Human access to Ops uses the `payment_ops` job (+ scope) — see [Partners overview → Humans vs machines](/developer-tools/partners/introduction#humans-ops-vs-machines-api-keys) and [IAM Overview](/api-reference/iam-overview).
</Note>

***

## Getting your Partner Key

Your **first** key cannot be bootstrapped via API — it's minted for you.

| Step | Who                              | What                                                     |
| ---- | -------------------------------- | -------------------------------------------------------- |
| 1    | TagadaPay                        | Activates your partnership (`par_xxx`)                   |
| 2    | TagadaPay / your account manager | Mints a **live** Partner Key in the Hub (`tp_sk_live_…`) |
| 3    | Your backend                     | Uses that key for all provisioning calls                 |

<Warning>
  **Use a live Partner Key for production merchants.** A test Partner Key (`tp_sk_test_…`) can only mint **test** keys — it cannot mint live CRM or Processing keys. Trying to do so returns `403 mode_escalation_forbidden`.
</Warning>

***

## CRM Keys (`sk_crm_…`) — the merchant control plane

A CRM Key authenticates the **public API** (`/api/public/v1/*`) for **one merchant** (`acc_xxx`): orders, stores, customers, subscriptions, promotions.

```ts theme={null}
const partner = new Tagada(process.env.TAGADA_PARTNER_KEY!);

const key = await partner.partners.crm.merchants.keys.create('acc_xxx');
// {
//   object: 'crm_key',
//   id: '…',
//   accountId: 'acc_xxx',
//   prefix: 'sk_crm_live_a1b2…',   // safe to store/log/display
//   token: 'sk_crm_live_a1b2c3…',  // RETURNED ONCE — store immediately
//   name: 'Your Partner — merchant_42',
//   createdAt: '2026-04-29T…',
// }

const keys = await partner.partners.crm.merchants.keys.list('acc_xxx');
await partner.partners.crm.merchants.keys.revoke(key.id);
```

<Note>
  **New format, backward compatible.** New CRM Keys are minted as `sk_crm_…`. Existing UUID tokens keep working unchanged — authentication looks up the presented token regardless of prefix. You don't have to migrate anything.
</Note>

<Warning>
  **The `token` field is only returned on creation.** Only the `prefix` is kept for display. If you lose the token, revoke and mint a new one.
</Warning>

A merchant client then uses that token directly:

```ts theme={null}
const merchant = new Tagada(crmKey.token); // sk_crm_live_…
await merchant.orders.list();
```

***

## Processing Keys (`tp_sk_…`) — charging & reporting

A Processing Key authenticates the **Processing API** (`/api/tagadapay/v1/*`) for **one TPA** (`tpa_xxx`): charges, refunds, balance transactions, disputes, payouts.

```ts theme={null}
const subKey = await partner.partners.processing.tpas.keys.create('tpa_xxx');
// optional: { mode: 'test' } for a test key
// {
//   object: 'processing_key',
//   id: 'ak_xxx',
//   tpaId: 'tpa_xxx',
//   prefix: 'tp_sk_live_a1b2c3d4',     // safe to store/log/display
//   secret: 'tp_sk_live_a1b2c3d4…',    // RETURNED ONCE — store immediately
//   label: 'Your Partner — merchant_42',
//   status: 'active',
//   createdAt: '2026-04-29T…',
// }

const keys = await partner.partners.processing.tpas.keys.list('tpa_xxx');
await partner.partners.processing.tpas.keys.revoke(subKey.id);
```

Labels are auto-assigned as **`{PartnerName} — {externalRef}`** (falls back to the TPA id when `externalRef` is missing). Set `externalRef` on the TPA so keys are easy to tell apart.

<Warning>
  **The `secret` field is only returned on creation.** Store it in your secret manager immediately. If you lose it, revoke and re-mint.
</Warning>

***

## Recommended: one record per merchant, per environment

```text theme={null}
your_db.merchants table:
  external_ref            = 'merchant_42'        // your id — used as externalRef
  tagada_merchant_id      = 'acc_xxx'            // CRM
  tagada_crm_key          = (encrypted sk_crm_live_…)
  tagada_tpa_id           = 'tpa_xxx'            // Processing
  tagada_store_id         = 'store_xxx'
  tagada_processing_key   = (encrypted tp_sk_live_…)
```

Don't share keys across merchants — the security boundary disappears if you do.

***

## Authenticating requests

All three keys use the same Bearer header; only the base URL differs by domain.

```bash theme={null}
# CRM domain
curl https://api.tagada.io/api/public/v1/orders \
  -H "Authorization: Bearer sk_crm_live_xxx"

# Processing domain
curl -X POST https://api.tagada.io/api/tagadapay/v1/payments/process \
  -H "Authorization: Bearer tp_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "storeId": "store_xxx", "paymentInstrumentId": "pi_xxx", ... }'
```

The SDK handles the header and the right base URL for you:

```ts theme={null}
const merchant = new Tagada(crmKey.token);       // routes CRM calls to /api/public/v1
const charging = new Tagada(processingKey.secret); // routes processing calls to /api/tagadapay/v1
```

***

## Scope enforcement

The server enforces scope on **every** request:

| Key                       | Endpoint                                  | Result                                                      |
| ------------------------- | ----------------------------------------- | ----------------------------------------------------------- |
| Partner key (payfac WL)   | `processing.tpas.create`                  | ✅ allowed                                                   |
| Partner key (CRM-only)    | `processing.tpas.create`                  | ❌ 403 `payfac_required` — use `applications.create` / embed |
| Partner key               | `processing.applications.create`          | ✅ allowed (sub-merchant `accountId` required)               |
| Partner key               | `crm.merchants.create`                    | ✅ allowed                                                   |
| Processing key            | `processing.tpas.create`                  | ❌ 403 — can't provision                                     |
| Processing key            | `payments.process`                        | ✅ only for its own `tpa_xxx`                                |
| Processing key for tpa\_A | `payments.process` with a store of tpa\_B | ❌ 403                                                       |
| CRM key                   | `orders.list`                             | ✅ only for its own `acc_xxx`                                |
| CRM key                   | `/api/tagadapay/v1/*` (processing)        | ❌ 403 — wrong domain                                        |

***

## Revocation & rotation

```ts theme={null}
// Mint the new key first, deploy it, THEN revoke the old one.
const next = await partner.partners.processing.tpas.keys.create('tpa_xxx');
await yourSecretManager.set('merchant_42.processing_key', next.secret);
// after confirming no traffic on the old key:
await partner.partners.processing.tpas.keys.revoke('ak_old_xxx');
```

Revocation is **immediate** and **irreversible** — subsequent calls with that secret get `HTTP 401`; in-flight calls complete. A `lastUsedAt` timestamp updates on every call, useful to confirm a key is safe to revoke.

***

## Test mode

Test keys carry a `_test_` segment (`tp_sk_test_…` for both Partner and Processing keys, `sk_crm_test_…` for CRM keys):

```ts theme={null}
const partnerTest = new Tagada(process.env.TAGADA_PARTNER_TEST_KEY!);
// Payfac WL only — CRM-only partners use applications.create / embed instead
const tpa = await partnerTest.partners.processing.tpas.create({ legalName: 'Acme SAS', accountId: 'acc_xxx', country: 'FR' });
const testKey = await partnerTest.partners.processing.tpas.keys.create(tpa.id); // tp_sk_test_…
```

Test mode routes to test processors, returns mock 3DS challenges, moves no real money, and lives in a fully isolated namespace — you can't mix test and live keys in the same client.
