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
Partners use two kinds of keys, each with a different scope and a different blast radius if it leaks.
The two-key model
┌─────────────────────────────────────────────────────────────────┐
│ PARTNER KEY │
│ tp_sk_partner_live_xxx │
│ │
│ Can: provision new TPAs, mint sub-keys, list TPAs, submit KYB │
│ Used by: your provisioning pipeline / signup flow │
│ Scope: ALL TPAs under your partnership │
└─────────────────────────────────────────────────────────────────┘
│
│ partners.apiKeys.create(tpaId)
▼
┌─────────────────────────────────────────────────────────────────┐
│ SUB-KEY (per merchant) │
│ tp_sk_live_xxx │
│ │
│ Can: tokenize, charge, refund, manage instruments — for ONE │
│ specific TPA only │
│ Used by: your charging code, in production │
│ Scope: a single TPA │
└─────────────────────────────────────────────────────────────────┘
| Partner key | Sub-key |
|---|
| Prefix | tp_sk_partner_* | tp_sk_* |
| Scope | All your TPAs | One TPA |
| Provisioning powers | ✅ partners.* | ❌ |
| Charging powers | ⚠️ requires explicit storeId per call | ✅ implicit (sub-key knows its TPA) |
| Where to use | Server-only, signup pipeline | Server-only, charging path |
| If leaked | All your merchants exposed | One merchant exposed |
Never use the partner key in your charging path. Mint a sub-key for each merchant on creation, store it next to the merchant’s record on your side, and use only the sub-key thereafter. This minimizes blast radius.
Mint a sub-key
const subKey = await partner.partners.apiKeys.create('tpa_xxx', {
label: 'merchant_42 — server charges',
});
// {
// id: 'ak_xxx',
// prefix: 'tp_sk_live_a1b2c3d4', // safe to store/log/display
// secret: 'tp_sk_live_a1b2c3d4...', // RETURNED ONCE — store immediately
// tagadapayAccountId: 'tpa_xxx',
// label: 'merchant_42 — server charges',
// createdAt: '2026-04-29T...',
// }
The secret field is only returned on creation. We hash it on storage and only keep the prefix for display. If you lose it, you must rotate.
Recommended: one key per merchant per environment
your_db.merchants table:
merchant_42:
tagada_tpa_id = 'tpa_xxx'
tagada_store_id = 'store_xxx'
tagada_subkey_live_id = 'ak_aaa'
tagada_subkey_live = (encrypted in your secret manager)
tagada_subkey_test_id = 'ak_bbb'
tagada_subkey_test = (encrypted in your secret manager)
Don’t share keys across merchants — the security boundary disappears if you do.
List sub-keys
const keys = await partner.partners.apiKeys.list('tpa_xxx');
// [{ id, prefix, label, createdAt, lastUsedAt, status }]
You see the prefix only. If you’ve lost the secret, your only path is to revoke and re-mint.
Revoke a sub-key
await partner.partners.apiKeys.revoke('ak_xxx');
// Subsequent calls with that secret get HTTP 401.
// Existing in-flight calls complete normally.
Revocation is immediate and irreversible. Mint a new key first, deploy it, then revoke the old one.
Rotation pattern
// 1. Mint a new key
const newKey = await partner.partners.apiKeys.create('tpa_xxx', { label: 'rotation 2026-04' });
// 2. Update your secret manager + redeploy your charging service
await yourSecretManager.set(`merchant_42.subkey_live`, newKey.secret);
// 3. After confirming no traffic on the old key (e.g. lastUsedAt > 24h ago), revoke
await partner.partners.apiKeys.revoke('ak_old_xxx');
A lastUsedAt timestamp is updated on every API call — useful to confirm safe revocation.
Authenticating requests
Both keys use the same Bearer header format:
Authorization: Bearer tp_sk_live_xxx
The SDK handles this for you:
const tagada = new Tagada(subKey.secret);
// All subsequent calls carry Authorization: Bearer tp_sk_live_xxx
If you call our API directly without the SDK:
curl -X POST https://api.tagadapay.com/api/public/v1/payments/process \
-H "Authorization: Bearer tp_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "storeId": "store_xxx", "paymentInstrumentId": "pi_xxx", ... }'
Scope enforcement
The server enforces scope on every request:
| Key type | Endpoint reached | Server check |
|---|
| Partner key | /v1/accounts (POST) | ✅ allowed |
| Partner key | /v1/payments/process | ⚠️ allowed only if storeId belongs to a TPA owned by this partner |
| Sub-key | /v1/accounts (POST) | ❌ 403 — sub-keys can’t provision new TPAs |
| Sub-key | /v1/payments/process | ✅ allowed only if storeId matches the sub-key’s TPA |
| Sub-key for tpa_A | /v1/payments/process with storeId of tpa_B | ❌ 403 |
Test mode
Test keys have prefix tp_sk_partner_test_* / tp_sk_test_*:
const partnerTest = new Tagada(process.env.TAGADA_PARTNER_TEST_KEY!);
const tpa = await partnerTest.partners.accounts.create({ legalName: 'Acme TEST', ... });
const subKeyTest = await partnerTest.partners.apiKeys.create(tpa.id, { label: 'test' });
Test mode:
- Routes payments to test processors (Stripe test, etc.)
- Returns mock 3DS challenges
- Doesn’t move real money
- Lives in a fully isolated namespace — you can’t mix test and live keys in the same client