The two credentials
A portal session token is what the portal API takes: an Auth0 bearer token in theAuthorization header, belonging to a person signed in to the Kintra CMS, with the tenant named by the {tenantId} segment of the path. Reach for it when you need something the gateway surface does not carry, which for most integrations means issuing the key below, or when you are building against a portal session a person already has.
A tenant API key is what the gateway API takes: a key id in a header, and a signature computed with that key’s secret. Your backend holds both, nobody is signed in anywhere, and the key resolves the tenant, so there is no tenant in the path. This is the credential your servers integrate with, and the rest of this page is about it.
Getting a key
The path is self-serve from end to end.- Kintra invites your team through its admin surface.
- Your team gets access to the Kintra CMS.
- In the CMS you create an API key for your tenant. The same operation is on the portal API as Create an API key.
- Your backend calls the gateway API with that key, signing every request.
secret, no read route returns secret afterwards, and a key whose secret was not kept has to be replaced rather than recovered. Store it where your backend can reach it before you move on.
Send the response’s key_id as X-Tenant-Key and sign with secret. They are two different values from the same response, and swapping them is a common first failure.
Nothing forces you down to a single key while you rotate. Create the replacement, cut your backend over to it, then revoke the old one, and your integration keeps working throughout. A revoke takes effect immediately and cannot be undone. Listing your keys shows which are active, when each was last used, and when each expires.
What a signed request carries
Every gateway request carries three headers.
All three names are read case-insensitively, so whatever casing your HTTP client picks is fine.
Writes carry a fourth header,
X-Idempotency-Key, which is required rather than optional on the routes that take it. The request conventions page lists those routes and says what a missing, replayed or reused key returns.
The signing string
X-Tenant-Signature is the lowercase hex digest of an HMAC-SHA256, keyed with the key’s secret, over four parts concatenated in this order with nothing between them:
timestampis the same value you put inX-Tenant-Timestamp, in Unix seconds.METHODis the HTTP verb in uppercase. The server uppercases before it hashes, so a lowercase verb in your signing string produces a different digest from the one it computes.urlis the path and query exactly as sent, starting at the/api/v1prefix:/api/v1/gateway/tiers?tier_name=gold, not/gateway/tiers. Every query parameter is part of it, in the order you send them.bodyis the request body for POST, PUT and PATCH, and the empty string for every other method.
Sign the bytes you send
One rule is behind most first-attempt failures. The server keeps no copy of your raw body: it hashes a re-serialization of the body it parsed. A client that pretty-prints its JSON for the request and signs a compact form of the same object therefore sends bytes that do not match the ones it signed, and gets a 401 with nothing else to go on. Serialize the body once, sign that exact string, and send that exact string. Both helpers below do that, and so does every sample in the reference.Timestamps
X-Tenant-Timestamp is Unix seconds, not milliseconds. It is checked before the signature, so a request whose timestamp is not current is rejected whatever it was signed with, and rejected without the secret being touched. Stamp each request as you send it and keep the sending machine’s clock synchronised.
Sign a request
Both helpers make the same call,GET /gateway/customers, which is the cheapest way to find out whether a new key works. Export the base URL for this environment first:
YOUR_TENANT_KEY_ID and YOUR_SIGNING_SECRET with the two values from the create response and run either one. A 200 means the credential and the signing string are both right.
node:crypto and fetch are both in Node 18 and later.
To turn either into a write, three things change and nothing else: METHOD becomes the verb you are calling, BODY becomes the compact JSON you are sending, and the request grows a Content-Type: application/json header and an X-Idempotency-Key. The signing line stays exactly as it is, because BODY is already one of its parts. The quickstart runs that version.
The same recipe on every operation page
Every gateway operation in the reference carries this pair of helpers filled in for that operation: its method, its path, its example body, its headers, with the signing lines unchanged from the ones above. They come from one template each, so a helper copied from an operation page and a helper copied from here build the signature the same way. The interactive playground on those pages is the exception. It cannot compute an HMAC, so pressing Send with only your key returns 401 unless you paste in a timestamp and a signature you built yourself.When a signature is rejected
Every failure of this check comes back the same way, 401 withAUTH_INVALID_CREDENTIALS:
message names which check failed, so read it first. Then work through the causes in the order they are checked:
- The timestamp is not current. Send the moment you are sending, in seconds, and check the clock on the machine sending it.
- The body signed is not the body sent. See Sign the bytes you send above.
- The url signed is not the url sent. The query string is part of it, and so is the
/api/v1prefix. - The verb in the signing string is not uppercase.
- The key id and the secret come from different keys, or the key has been revoked or has expired.

