Skip to main content

Apply for TagadaPay Processing

Who this is for: a direct merchant who wants TagadaPay to process their cards — the same flow as Dashboard → New Request at dashboard.tagadapay.io.Not a partner? You’re in the right place. Authenticate with a CRM key (sk_crm_…) and call processing.applications.create().Building a platform for many sub-merchants? Use the Partners guide and partners.processing.tpas.create() instead — partners never use this endpoint.
A processing application is how you ask TagadaPay to onboard you as a processor customer. Submit it with a CRM key, it lands in our review queue, our team runs KYB, and provisions your TPA. The payload mirrors the dashboard “New Request” form 1:1 — the exact data our acquiring network runs KYB/KYC on. The more complete and accurate it is, the faster you are approved.

What you’re applying for: a TPA

Approval gives you a TagadaPay Account — a TPA (id prefix tpa_xxx). The TPA, not your login and not your store, is the object that actually holds one KYB-approved legal entity, its assigned acquirer, its settlement bank account, and its own charging scope. It sits under your merchant account — the thing you log into and hold a single CRM key for:
Merchant (acc_xxx)            your Tagada account: one login, one CRM key
  ├── TPA (tpa_xxx)           an approved legal entity that can charge cards
  └── Store (store_xxx)       checkout & payment routing (auto-created)
The important part: one application provisions one TPA, and a single merchant can own many of them (acc_xxx → 0..N tpa_xxx). See One merchant, many TPAs for why you’d want that and how to do it.

The three field tiers

Every field is one of three tiers — the SDK types tag each one, and your editor autocompletes the full set:

Required

The API rejects the application without it (400 missing_required_fields). Non-optional in the types.

Recommended

Not enforced at intake, but acquirers will ask for it before activation. Omitting one only delays approval — create() echoes the gaps in recommendations, and the review queue flags every one.

Optional

Situational / nice-to-have. Improves risk underwriting but is never blocking.
Only 5 fields are strictly required: businessInfo.businessName, businessInfo.country, and the representative’s firstName, lastName, email. Everything else is recommended or optional — but a 5-field application will bounce back from KYB. Send a complete one.

Minimal vs complete

import { Tagada } from '@tagadapay/node-sdk';

const tagada = new Tagada({ apiKey: process.env.TAGADA_CRM_KEY }); // sk_crm_…

// Minimal — passes intake, but our team will ask for more before activation.
// NOTE: this is NOT idempotent. Each call submits a NEW application to the
// review queue — keep `draft.id` and poll with retrieve(), don't blindly retry.
const draft = await tagada.processing.applications.create({
  businessInfo: { businessName: 'ACME COMMERCE', country: 'FR' },
  representative: { firstName: 'Jeanne', lastName: 'Dupont', email: 'jeanne@example.com' },
});

console.log(draft.recommendations);
// → ['businessInfo.email', 'businessInfo.website', 'businessInfo.registrationNumber',
//    'businessInfo.mcc', 'representative.idNumber', 'bankAccount.iban', …]
A complete application — the shape you actually want to send. The payload is the same everywhere; only the country-specific identifiers and settlement rail change. Pick your jurisdiction:
const application = await tagada.processing.applications.create({
  businessInfo: {
    // ── Required ──
    businessName: 'ACME COMMERCE',
    country: 'FR',
    // ── Recommended (acquirer KYB) ──
    activityType: 'business',
    legalEntityType: 'sas',
    registrationNumber: '123456789',   // SIREN / company number (from KBIS)
    taxId: 'FR00123456789',            // VAT number
    website: 'https://example.com',
    mcc: '5734',
    email: 'ops@example.com',
    phone: '+33123456789',
    address: {
      street: '1 Rue de l’Exemple',
      city: 'Paris',
      postalCode: '75001',
      country: 'FR',
    },
    // ── Optional (risk profile) ──
    businessModel: 'SaaS (subscription)',
    businessDescription: 'Online software platform (example.com).',
    monthlyVolume: '10000',
    desiredCurrencies: ['EUR'],
  },
  representative: {
    // ── Required ──
    firstName: 'Jeanne',
    lastName: 'Dupont',
    email: 'jeanne@example.com',
    // ── Recommended (KYC) ──
    phone: '+33123456789',
    title: 'signatory',
    dateOfBirth: '1990-01-01',
    nationality: 'FR',
    idType: 'identityCard',
    idNumber: 'X0000000',
    idCountry: 'FR',
    idExpiry: '2030-01-01',
    residentialAddress: {
      street: '2 Avenue Imaginaire',
      city: 'Paris',
      postalCode: '75002',
      country: 'FR',
    },
  },
  // ── Recommended: settlement account (holder MUST match the entity) ──
  bankAccount: {
    accountHolderName: 'SAS ACME COMMERCE',
    iban: 'FR7630006000011234567890189',  // SEPA rail
    bic: 'AGRIFRPPXXX',
    currency: 'EUR',
    country: 'FR',
  },
  documents: [],
});
Either way, create() returns the same shape:
// → {
//   object: 'processing_application',
//   id: 'ent_xxx',
//   status: 'submitted',
//   accountId: 'acc_xxx',
//   tpaId: null,            // set once our team provisions your TPA
//   kycStatus: null,
//   submittedAt: '2026-06-26T…',
//   recommendations: []     // empty → nothing left to fill
// }

Field reference

businessInfo

FieldTierNotes
businessNameRequiredLegal entity name exactly as registered (or the person’s full name for a sole trader).
countryRequiredCountry of registration — ISO 3166-1 alpha-2 (FR).
activityTypeRecommended'business' or 'individual'. Drives the acquirer legal-entity type.
legalEntityTypeRecommendedCanonical code: sas, sarl, sa, auto_entrepreneur, llc, ltd, inc, corporation
registrationNumberRecommendedCompany registration number from the registration certificate / KBIS. FR: SIREN/SIRET, UK: Companies House no., US: the 9-digit EIN (see note below).
taxIdRecommendedTax identifier. EU: the VAT number, e.g. FR00123456789. US: the federal EIN — the same value as registrationNumber.
websiteRecommendedPublic URL — also used for the statement descriptor & content review.
mccRecommendedMerchant Category Code, e.g. 5734.
addressRecommendedRegistered business address (street, city, postalCode recommended; apartment, state, country optional).
emailRecommendedBusiness contact email (falls back to the representative’s).
phoneRecommendedBusiness phone, E.164 (+33…).
businessDescriptionOptionalPlain-language description of what is sold.
businessModelOptionalOne-liner, e.g. SaaS (subscription).
desiredCurrenciesOptionalSettlement currencies — ISO 4217, e.g. ['EUR'].
mccBreakdownOptionalPer-MCC volume split ({ code, label?, volume?, averageTicket?, chargebackRatio?, refundRatio? }[]).
monthlyVolumeOptionalExpected monthly card volume, major units as a string ('10000').
averageTicketOptionalTypical order value, major units as a string.
currentlyProcessingOptionalboolean — already processing elsewhere today?
currentProcessorOptionalCurrent/previous processor name.
previousMonthlyVolumeOptionalVolume processed elsewhere last month, as a string.
expectedStartDateOptionalISO date YYYY-MM-DD.
supportEmail / supportPhoneOptionalCustomer-support contacts shown to cardholders.
US merchants — registrationNumber and taxId are the same EIN. The US has no separate company registration number, so the federal EIN (e.g. 98-1923816) is both your tax ID and your registration number. Send the same 9-digit value in both fields. The acquirer requires both to be present.

representative

The legal representative — the UBO / authorised signer who is the KYC subject.
FieldTierNotes
firstNameRequiredGiven name.
lastNameRequiredFamily name.
emailRequiredContact email — also where the confirmation is sent.
phoneRecommendedE.164 (+33…).
titleRecommendedRole: signatory, uboThroughOwnership, uboThroughControl, director
dateOfBirthRecommendedISO YYYY-MM-DD.
nationalityRecommendedISO 3166-1 alpha-2.
idNumberRecommendedGovernment ID number.
idTypeRecommendedpassport · identityCard · driversLicense · nationalIdNumber.
idCountryRecommendedIssuing country — ISO 3166-1 alpha-2.
idExpiryRecommendedISO YYYY-MM-DD.
residentialAddressRecommendedHome address (street, city, postalCode recommended) — not the business seat.
idStateOptionalIssuing state/province, where relevant (US).
ssnLast4OptionalLast 4 of SSN — US sole traders only.

bankAccount

Settlement (payout) account. The account holder must match the legal entity — acquirers reject payouts to a mismatched name. Provide one rail.
FieldTierNotes
accountHolderNameRecommendedMust equal the legal entity name.
currencyRecommendedSettlement currency — ISO 4217 (EUR).
iban + bicRecommendedSEPA rail.
routingNumber + accountNumberUS ACH rail (use instead of IBAN).
bsbNumber + accountNumberAustralian BSB rail.
bankName / countryOptionalBank name and country (ISO 3166-1 alpha-2).

documents

Optional at submit, but they satisfy KYB requirements early. Each entry is metadata referencing a file you’ve already uploaded to storage:
documents: [
  { type: 'national_id', fileKey: 'kyc/loic-id.pdf', fileUrl: 'https://…', fileName: 'id.pdf' },
  { type: 'business_registration', fileKey: 'kyb/kbis.pdf', fileUrl: 'https://…' },
  { type: 'bank_statement', fileKey: 'kyb/rib.pdf', fileUrl: 'https://…' },
]
Accepted type values: passport, drivers_license, national_id, proof_of_national_id, proof_of_address, business_registration, bank_statement, processing_history, id_scan, other.

How recommendations works

create() never fails on a missing recommended field. Instead it returns them so you can surface them in your own UI or fill them later:
const app = await tagada.processing.applications.create({ businessInfo, representative });

if (app.recommendations?.length) {
  console.warn('Acquirer KYB will need these before activation:', app.recommendations);
}
The same gaps appear as “Missing” pills in the TagadaPay dashboard once your application is in review.

Why the SDK asks for less than the dashboard “requires”

The dashboard form visually marks ~15 fields as required (red pills). Only 5 are hard gates; the rest are the Recommended tier above. A complete SDK application and a complete dashboard application carry identical data.

Track the application

const status = await tagada.processing.applications.retrieve('ent_xxx');
// { status: 'submitted' | 'in_review' | 'approved' | 'rejected' | …, tpaId, kycStatus }
Once approved and your TPA is provisioned, tpaId is populated. Then enroll to mint a processing key and start charging.

One merchant, many TPAs

A TPA is scoped to one legal entity with one settlement setup. That is deliberate: KYB is run per entity, and an acquirer is assigned per entity. So whenever your reality has more than one of those, you want more than one TPA — all owned by your single merchant account. Common reasons to run several:
You have…TPAs you’d open
Several legal entities (a holding with multiple LLCs, one company per brand)One TPA per entity — each passes its own KYB
The same entity selling in several regionsOne TPA per settlement region (e.g. an EU TPA settling in EUR, a US TPA settling in USD)
Risk or volume you want to splitSeveral TPAs so traffic and exposure spread across different acquirers
One application = one TPA. You don’t get multiple TPAs from a single submission — you get them by submitting one application per entity (or region). Every application you send with the same CRM key attaches to the same merchant (acc_xxx), so they all collect under one account you manage with one key.

Example: three LLCs, one merchant

Say you operate three US LLCs. Each is a separate legal entity, so each needs its own application — but you submit them all from the same CRM key:
import { Tagada } from '@tagadapay/node-sdk';

const tagada = new Tagada({ apiKey: process.env.TAGADA_CRM_KEY }); // one CRM key

// Three LLCs you operate. Each is its own legal entity → its own TPA.
const entities = [
  {
    businessName: 'NORTHWIND APPAREL LLC',
    ein: '88-1112221', mcc: '5651', website: 'https://northwind.example',
    rep: { firstName: 'John', lastName: 'Smith', email: 'john@northwind.example' },
    accountNumber: '1000000001',
  },
  {
    businessName: 'CINDER SUPPLEMENTS LLC',
    ein: '88-3334441', mcc: '5499', website: 'https://cinder.example',
    rep: { firstName: 'John', lastName: 'Smith', email: 'john@cinder.example' },
    accountNumber: '1000000002',
  },
  {
    businessName: 'HARBOR SAAS LLC',
    ein: '88-5556661', mcc: '5734', website: 'https://harbor.example',
    rep: { firstName: 'John', lastName: 'Smith', email: 'john@harbor.example' },
    accountNumber: '1000000003',
  },
];

// One application per LLC. They all attach to the SAME merchant (acc_xxx)
// and each provisions its own TPA once approved.
for (const e of entities) {
  const app = await tagada.processing.applications.create({
    businessInfo: {
      businessName: e.businessName,
      country: 'US',
      activityType: 'business',
      legalEntityType: 'llc',
      registrationNumber: e.ein,   // US: EIN is both…
      taxId: e.ein,                // …registrationNumber and taxId
      mcc: e.mcc,
      website: e.website,
      address: { street: '1 Market Street', city: 'San Francisco', state: 'CA', postalCode: '94105', country: 'US' },
    },
    representative: e.rep,
    bankAccount: {
      accountHolderName: e.businessName,  // holder MUST match each entity
      routingNumber: '021000021',
      accountNumber: e.accountNumber,
      currency: 'USD',
      country: 'US',
    },
  });

  console.log(`${e.businessName} → application ${app.id}, merchant ${app.accountId}`);
  // Same app.accountId (acc_xxx) for all three — one merchant, three applications.
}
Same entity, several regions? Use the exact same loop, but vary businessInfo.country and the settlement rail per region — e.g. one application with country: 'FR' + an EUR iban/bic, and another with country: 'US' + a USD routingNumber/accountNumber. You’ll end up with an EU TPA and a US TPA under the same merchant.

After approval: one key for all your TPAs

Once your applications are approved and their TPAs are provisioned, call enroll() once. It mints a merchant-scoped processing key (tp_sk_…) that can see and manage every TPA under your account — you do not enroll per TPA.
// Build a client with your CRM key, then enroll → processing key.
// NOTE: not idempotent. One call = one NEW key. Enroll ONCE and store the
// secret; calling again just mints extra live keys you'll have to revoke.
const { key } = await tagada.processing.enroll({ mode: 'live' });
// key.secret = 'tp_sk_live_…'  (plaintext, returned ONLY here — store it)

const merchant = new Tagada({ apiKey: key.secret }); // tp_sk_live_…

// This one key lists all of your TPAs.
const { data: tpas } = await merchant.processing.tpas.list();
// → [ { id: 'tpa_aaa', … }, { id: 'tpa_bbb', … }, { id: 'tpa_ccc', … } ]
//   one TPA per approved LLC, all under your single merchant account

// Optional: mint a key scoped to a SINGLE TPA — e.g. to isolate one brand's
// charging, or hand one brand's key to a teammate.
// Also not idempotent: each call mints another key. Mint one per consumer
// (per service / per teammate) on purpose, and revoke them when unused.
const northwindKey = await merchant.processing.tpas.keys.create('tpa_aaa');
// northwindKey.secret = 'tp_sk_live_…' scoped to tpa_aaa only
enroll() is activation without leaving your code. It does the same thing as going to the TagadaPay dashboard, activating processing, and copying your key — the SDK just lets you do it (and the whole apply → KYB → charge flow) end to end, without ever opening the CRM or the dashboard.Because of that, treat key minting as a one-time setup step, not application code: run enroll() once in a bootstrap script (or grab the key from the dashboard), store key.secret in your secrets manager, and have your app read it from the environment. Minting a key inside your request path is the equivalent of re-running a seeding script on every request.
// ── setup.ts — run ONCE ────────────────────────────────────────────────────
const { key } = await tagada.processing.enroll({ mode: 'live' });
// → store key.secret as TAGADA_PROCESSING_KEY in your secret manager

// ── app.ts — every request ─────────────────────────────────────────────────
const tp = new Tagada({ apiKey: process.env.TAGADA_PROCESSING_KEY! }); // read it
await tp.payments.process({ /* … */ });
So the key model mirrors the entity model:
KeyScopeMinted bySees
CRM key (sk_crm_…)the merchanttagada-init / dashboardCRM data + apply + enroll()
Merchant processing key (tp_sk_…)the merchantprocessing.enroll()all your TPAs
Per-TPA key (tp_sk_…)one TPAprocessing.tpas.keys.create(tpaId)that one TPA
enroll()’s mode defaults to 'test' server-side, so a fresh enrollment can never touch live processing by accident. Pass mode: 'live' once you’re ready to charge real cards.
enroll() is not idempotent — call it once and store the secret. Every call mints a brand-new merchant key; it does not return or rotate a previous one. Calling it repeatedly doesn’t break anything (older keys keep working), but you pile up live credentials you then have to track and revoke. Treat key.secret like a password: persist it on first enroll, reuse it, and if you ever lose or leak one, revoke that key and enroll again. For narrower, disposable credentials, mint a per-TPA key with processing.tpas.keys.create(tpaId) instead.

Calling things more than once

Networks time out and retries happen, so it pays to know what each call does the second time. The rule of thumb: reads are safe to repeat, and every write here creates a new resource — none of them silently de-duplicate. Know which is which before you wrap anything in a retry loop.
CallWhat it doesSafe to repeat?A second call…
processing.applications.create()Submits a processing applicationNo…files a second application in the review queue. Duplicates slow KYB and can provision an extra TPA.
processing.enroll()Mints your merchant processing keyNo…mints another live key. Old ones keep working — you just accumulate secrets to track and revoke.
processing.tpas.keys.create()Mints a per-TPA keyNo…mints another key. Great on purpose (one per service), a liability by accident.
processing.tpas.list() / retrieve()Reads your TPA(s)Yes…returns the same data. Repeat freely.
payments.process()Charges a cardOnly with an idempotency key…can double-charge without one; with a stable key it returns the original payment.

Retry a charge safely

payments.process() is the one write you legitimately need to retry (timeouts mid-charge). Pass a stable idempotencyKey: the first call charges, any repeat with the same key returns that same payment instead of charging again.
const tp = new Tagada({ apiKey: key.secret }); // your stored tp_sk_…

await tp.payments.process(
  { paymentInstrumentId, paymentFlowId, amount: 4999, currency: 'EUR', paymentMethod: 'card', mode: 'purchase' },
  { idempotencyKey: 'order_42_charge_1' }, // same key on retry => at most one charge
);

Don’t re-submit an application

applications.create() has no server-side de-dup, so a retry creates a duplicate dossier. Keep your own “already applied?” guard, and once you have an id, poll instead of re-submitting:
const app = await tagada.processing.applications.create({ businessInfo, representative });

// From here on, track it — never call create() again for the same entity.
const status = await tagada.processing.applications.retrieve(app.id);

Common errors

HTTPCodeWhen
400missing_required_fieldsOne of the 5 required fields is empty (missing lists them).
400invalid_requestBody failed schema validation (issues has details).
401invalid_api_keyNot a valid CRM key (sk_crm_…).
403use_applications_apiA direct merchant tried tpas.create() — use this endpoint instead.

Next steps

Node SDK Quick Start

CRM setup — stores, products, funnels, payment flows

Partners — provision sub-merchants

If you onboard merchants on their behalf, use partners.processing.tpas.create() instead