> ## 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.

# Merchant automation (REST)

> The raw HTTP routes behind both domains — for backends that do not use the Node SDK.

# Merchant automation (REST)

The Node SDK is the recommended path, but every partner operation is a plain HTTP call. This page is the route reference for backends in any language. All write calls require your **Partner Key** (`tp_sk_live_…` / `tp_sk_test_…`, partner-scoped), minted in the **Partner Hub**.

The routes split along the platform's two domains:

| Domain         | Base URL                                         | Provisions                                     |
| -------------- | ------------------------------------------------ | ---------------------------------------------- |
| **CRM**        | `https://api.tagada.io/api/public/v1/partner`    | merchants (`acc_xxx`) + CRM Keys (`sk_crm_…`)  |
| **Processing** | `https://api.tagada.io/api/tagadapay/v1/partner` | TPAs (`tpa_xxx`) + Processing Keys (`tp_sk_…`) |

There is **no cross-partner access** — every call is scoped to your `par_xxx`.

***

## Authentication

```http theme={null}
Authorization: Bearer tp_sk_live_…
```

Only **Partner Keys** can provision merchants/TPAs or mint keys on their behalf. Scoped keys (CRM/Processing) cannot provision.

<Warning>
  A **test** Partner Key can only mint **test** keys. Minting a live key with a test Partner Key returns `403 mode_escalation_forbidden`.
</Warning>

***

## CRM domain — merchants & CRM Keys

Base: `/api/public/v1/partner`

| Method   | Path                     | Description                                                                    |
| -------- | ------------------------ | ------------------------------------------------------------------------------ |
| `POST`   | `/merchants`             | Create a merchant (`acc_xxx`). CRM-only is valid. Idempotent on `externalRef`. |
| `GET`    | `/merchants`             | List merchants. `?externalRef=…` to look one up, `?limit=&cursor=` to page.    |
| `GET`    | `/merchants/:accId`      | Retrieve a merchant.                                                           |
| `POST`   | `/merchants/:accId/keys` | Mint a CRM Key — `token` (`sk_crm_…`) returned once.                           |
| `GET`    | `/merchants/:accId/keys` | List CRM Keys (prefix only).                                                   |
| `DELETE` | `/keys/:keyId`           | Revoke a CRM Key.                                                              |

```bash theme={null}
# Create a merchant
curl -X POST https://api.tagada.io/api/public/v1/partner/merchants \
  -H "Authorization: Bearer tp_sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "legalName": "Acme SAS", "externalRef": "merchant_42", "country": "FR", "currency": "EUR" }'

# Mint a CRM Key for it
curl -X POST https://api.tagada.io/api/public/v1/partner/merchants/acc_xxx/keys \
  -H "Authorization: Bearer tp_sk_live_…"
# → { "object": "crm_key", "id": "…", "accountId": "acc_xxx", "token": "sk_crm_live_…", ... }
```

***

## Processing domain — TPAs & Processing Keys

Base: `/api/tagadapay/v1/partner`

| Method   | Path                              | Description                                                                                      |
| -------- | --------------------------------- | ------------------------------------------------------------------------------------------------ |
| `POST`   | `/tpas`                           | Create a TPA (`legalName` + optional `accountId` in body). Idempotent on `externalRef`.          |
| `GET`    | `/tpas`                           | List TPAs (scoped to your partner key). `?externalRef=&limit=&cursor=`.                          |
| `GET`    | `/tpas/:tpaId`                    | Retrieve a TPA.                                                                                  |
| `GET`    | `/tpas/:tpaId/requirements`       | List KYB requirements.                                                                           |
| `PATCH`  | `/tpas/:tpaId/requirements/:code` | Submit a value for a non-document requirement (`{ value }`). Flips it to `pending_verification`. |
| `GET`    | `/tpas/:tpaId/documents`          | List KYB documents.                                                                              |
| `POST`   | `/tpas/:tpaId/documents`          | Record a KYB document (metadata).                                                                |
| `POST`   | `/tpas/:tpaId/keys`               | Mint a Processing Key — `secret` (`tp_sk_…`) returned once.                                      |
| `GET`    | `/tpas/:tpaId/keys`               | List Processing Keys (prefix only).                                                              |
| `DELETE` | `/keys/:keyId`                    | Revoke a Processing Key.                                                                         |

```bash theme={null}
# Create a TPA for an existing merchant
curl -X POST https://api.tagada.io/api/tagadapay/v1/partner/tpas \
  -H "Authorization: Bearer tp_sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "legalName": "Acme SAS", "accountId": "acc_xxx", "externalRef": "merchant_42_eu", "country": "FR", "currency": "EUR" }'

# Mint a Processing Key for it
curl -X POST https://api.tagada.io/api/tagadapay/v1/partner/tpas/tpa_xxx/keys \
  -H "Authorization: Bearer tp_sk_live_…"
# → { "object": "processing_key", "id": "ak_xxx", "tpaId": "tpa_xxx", "secret": "tp_sk_live_…", ... }
```

<Note>
  **Direct (non-partner) merchants** use the same Processing routes **without** the `/partner` segment — e.g. `POST /api/tagadapay/v1/tpas` — authenticated with their own Processing Key. The `/partner` prefix is what marks an "on behalf of" call.
</Note>

***

## Target exists and belongs to you

Every mint/list/revoke validates the target **before** acting:

1. The merchant/TPA **exists** — else `404` (`merchant_not_found` / `tpa_not_found`).
2. It **belongs to your partnership** — else `403` (`merchant_access_denied` / `tpa_access_denied`).

You cannot mint keys for something you have not provisioned. Create it first, then mint on the returned id.

### Common error responses

| HTTP | Code                                           | When                                                    |
| ---- | ---------------------------------------------- | ------------------------------------------------------- |
| 401  | `missing_api_key`                              | No `Authorization: Bearer` header                       |
| 401  | `invalid_api_key`                              | Unknown or revoked key                                  |
| 403  | `partner_scope_required`                       | Not a Partner Key (e.g. a scoped key used to provision) |
| 403  | `merchant_access_denied` / `tpa_access_denied` | Target belongs to another partner                       |
| 403  | `mode_escalation_forbidden`                    | Test Partner Key minting a live key                     |
| 404  | `merchant_not_found` / `tpa_not_found`         | Target id does not exist                                |
| 404  | `api_key_not_found`                            | Revoke target not found or already revoked              |

***

## Recommended partner-side mapping

```text theme={null}
your_merchants table:
  external_ref          → externalRef / Idempotency-Key on create
  tagada_merchant_id    → acc_xxx                (CRM)
  tagada_crm_key        → encrypted sk_crm_live_…
  tagada_tpa_id         → tpa_xxx                (Processing)
  tagada_store_id       → store_xxx   (from stores.list({ accountId }))
  tagada_processing_key → encrypted tp_sk_live_…
```

Store secrets in your vault immediately — `token`/`secret` cannot be retrieved later.

***

## End-to-end (SDK)

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

// 1. CRM: create the merchant + a CRM Key
const merchant = await partner.partners.crm.merchants.create({
  legalName: 'Acme SAS',
  externalRef: 'merchant_42',
});
const crmKey = await partner.partners.crm.merchants.keys.create(merchant.id);

// 2. Processing — pick the path that matches your contract:
//    • Payfac WL: tpas.create (below)
//    • CRM-only: applications.create({ accountId: merchant.id, … }) then enroll after promote
//      → see /developer-tools/partners/accounts and processing-applications
const tpa = await partner.partners.processing.tpas.create({
  legalName: 'Acme SAS',       // required — the KYB legal entity
  accountId: merchant.id,      // attach to the existing merchant (acc_xxx)
  externalRef: 'merchant_42_eu',
  country: 'FR',
  currency: 'EUR',
});
const processingKey = await partner.partners.processing.tpas.keys.create(tpa.id);

// 3. Discover the auto-created store
const stores = await partner.stores.list({ accountId: merchant.id });
const storeId = stores.data[0].id;

// 4. Persist on your side
await yourVault.put(merchant.externalRef ?? merchant.id, {
  crmKey: crmKey.token,
  processingKey: processingKey.secret,
  storeId,
});
```

This is the pattern any embedded PSP, marketplace, or whitelabel **payfac** partner should follow — not a one-off integration. CRM-only partners stop after step 1 on the CRM side and use [entity applications](/developer-tools/node-sdk/processing-applications) (or embed) instead of `tpas.create`.
