Skip to main content
The Quickstart registers a customer with the required email address and optional identity details. Onboarding a real user usually carries more: the attribute values your integration already knows, and the cohort the customer belongs in. All of it travels in the same request, so this page is one API call and a close reading of its payload. You need an API key id and its signing secret, and curl, openssl and jq. Authentication covers where the credential comes from, what every request carries, and what to check when one is rejected. A credential you have never used is better tested on the quickstart’s read than on the write below, which creates a record.

Before you start

Export the base URL for this environment:
Then hold your credentials and split that base URL into the host you send to and the path the signature covers:

The payload in full

Everything POST /gateway/customers accepts, in one body:
Only email is required. phone, first_name, last_name, date_of_birth, attributes and cohort are optional, and each of cohort’s three members is optional on its own.
string
required
One customer per address under your tenant. An address already registered is refused 409 with USER_ALREADY_EXISTS, and the refusal hands back no id, so take the id from the response here rather than planning to look one up by address later.
string
It is stored exactly as sent apart from leading and trailing whitespace; it is not format-validated, not unique, not searchable, not usable for lookup and not selectable in cohorts.
string
A plain string.
string
A plain string.
string
When supplied, it is written YYYY-MM-DD and validated against that format alone. A body missing email, or carrying a date in another shape, is refused 422 with VALIDATION_INVALID_INPUT before anything is created.
object
Initial values for the typed attributes your tenant has defined. A definition is your own field on the customer record, a key and a type, one of string, number, date or string[]. It is created once, in the KINTRA CMS’s Attributes page or with the portal API’s Create an attribute definition; the gateway supplies values and never defines fields. Each entry here is keyed by the attribute’s key, read case-insensitively, or by its definition id, and the value has to fit the definition’s type: a string, a number, an array of strings, or null to store the attribute cleared. The whole map is validated before the customer is created, so a bad entry cannot leave a half-onboarded record behind: a key that names no definition of your tenant, a value of the wrong type, or one definition named twice under two keys is refused 422 with VALIDATION_INVALID_INPUT, and the response names each entry it refused.
object
Where the customer sits in your program. country_id names an active country and club_ids active clubs, both by the ids your KINTRA CMS shows; an unknown or inactive id is refused 400 with VALIDATION_INVALID_INPUT. metadata is yours: an object stored as sent, for whatever your integration wants to keep about how this customer arrived.

Send it

One write carries all of it. The block below sends the required email, the optional profile fields, one attribute and the cohort metadata. Replace YOUR_ATTRIBUTE_KEY and its value with an attribute your tenant has defined, or drop the attributes member entirely if it has none yet, and widen the body from the payload above as your integration needs:
A 201 with an id is the answer you want. Store CUSTOMER_ID against your own user record. Nothing you send names your user id, so this is the only direction the mapping runs.
X-Idempotency-Key is required on this route: without it the request returns 400 with IDEMPOTENCY_KEY_REQUIRED and no customer is created. A fresh random value per write is the right habit, and it is what makes a retry after a timeout safe: replaying a key returns the stored response rather than creating a second customer.

What came back

The response carries the record as created: the id everything else takes, a username derived from the address, the status and onboarding_status the account starts in, and internal_wallets, the wallet KINTRA made for the customer in the same write. No request body on the gateway surface carries a wallet or an address field. The platform creates the wallet, and the XP this customer goes on to earn is held against it. Attributes and cohort are stored but not echoed: read them back with GET /gateway/customers/{customer_id} when you want to see the record as the API now holds it, rather than trusting what you sent.

The refusals

Every way this request says no, in the order worth checking when it does:
  • 400 with IDEMPOTENCY_KEY_REQUIRED. The write went out without X-Idempotency-Key.
  • 422 with VALIDATION_INVALID_INPUT for the body: a missing email address, a supplied date of birth in the wrong shape, or an attribute entry the validation refused. The response names what failed, and nothing was created.
  • 400 with VALIDATION_INVALID_INPUT for the cohort: a country or club id that is unknown or inactive.
  • 409 with USER_ALREADY_EXISTS. That address is already a customer of your tenant. Reading it as “this one is already in” is reasonable during an import, but the refusal hands back no id, so capture ids as you create rather than after.
error.details and the entry lists are optional across the API, so read them when they are there and branch on the code.

Where to go next

  • Award and redeem XP to give the customer you just onboarded a balance and spend it: the grant, the reads, a reward and a redemption, run end to end.
  • Customers for the record itself: statuses, the wallet, and what a read returns.
  • Request conventions for the error envelope those refusals arrive in, and the full idempotency rules.
  • Create an attribute definition on the portal surface for defining your own fields: what it creates is what this page’s attributes map may name.