Authentication
Secret keys, environments, permissions and the short-lived tokens the browser is allowed to hold
Every request to the TrustWix API is authenticated with a secret API key sent as a bearer token.
curl https://api.trustwix.com/v1/verifications \
-H "Authorization: Bearer TWK_..." \
-H "Content-Type: application/json"Two kinds of credential
A secret key identifies your account and carries the permissions attached to it. It reads TWK_
followed by one long opaque token and has no other structure. Secret keys belong on your server and
nowhere else.
A client token is minted for you when you create a verification and is returned as
client_token on the create response. It is scoped to that single verification, expires within
minutes, and is the only credential that may be handed to a browser or a mobile client. Treat it as
an opaque string, never parse it, and never cache it beyond the session it belongs to.
Where keys come from
Keys are created in your dashboard, under API keys. There is no endpoint that mints a key, so nothing in your integration ever needs a credential powerful enough to create more credentials. Creating, rotating and revoking are all owner or developer actions, and each one asks you to re-authenticate first, because each either hands out a working credential or takes one away.
The secret is shown exactly once, at the moment you create it. We keep only a hash and a short non-secret prefix, so we cannot show it to you again and neither can support. Copy it into your secret manager there and then.
Rotating mints a fresh secret on the same key and keeps its name, environment and permissions, so the key's identity in your logs survives the rotation. Deploy the new secret, confirm traffic has moved, and only then revoke what it replaced. Revocation takes effect immediately, including for requests already in flight, so doing it in the other order causes an outage.
If a key leaks
Revoke it in your dashboard straight away. A revoked key stops working for every request and can never be reactivated.
Environments
The environment is a property of the key itself, not a different host and not a header. One base URL serves both.
| Environment | Behaviour |
|---|---|
| Sandbox | No real checks run. Magic applicant values force the verdict. |
| Live | Real checks run against real submissions. |
You pick the environment when you create the key, and it is fixed for the life of that key. Because
the token carries no environment marker, there is nothing in the string to parse: read it from your
dashboard, where every key is listed against the environment it belongs to, or from mode on any
verification the key creates.
One wrinkle worth knowing before it surprises you. On the wire, a sandbox key's environment reads
test. It is the same split your dashboard calls Sandbox and Live, under an older spelling kept so
existing integrations keep parsing, and you will meet it on GET /v1/entitlements. The mode field,
which is the one that appears on every verification, always says sandbox or live.
Sandbox and live data never mix. A sandbox key cannot read a live verification and a live key cannot read a sandbox one, so an accidental cross-environment call fails cleanly rather than returning the wrong record.
Permissions
Every key carries a set of scopes. Calling an endpoint your key is not scoped for returns
403 permission_error with the code INSUFFICIENT_PERMISSIONS, and you can read the scopes on the
key you are currently holding from GET /v1/entitlements.
| Scope | What it unlocks |
|---|---|
verification:create | Create a verification, request and confirm uploads, record consent, decide an OPEN manual review, preview a flow |
verification:reopen | Reopen a verification that already has a decision, returning it to manual review and clearing its verdict |
verification:read | Retrieve and list verifications, list manual reviews, read applicant records |
applicant:read | The include=applicant,documents expansion: the identity read from the document, and the captured images |
webhook:manage | List, create and delete webhook endpoints |
analytics:read | The usage summary |
settings:read | Read your flows, your merchant profile and your entitlements |
settings:write | Reserved for the surfaces you manage in the dashboard rather than over the API |
verification:delete | Reserved. No endpoint uses it today |
console:session | Exchange the key for a back-office session acting as one of your people |
What a new key gets
The default set differs by environment, deliberately. A live key starts narrower than a sandbox one, because the blast radius is not the same.
| Scope | Sandbox default | Live default |
|---|---|---|
verification:create | yes | yes |
verification:read | yes | yes |
webhook:manage | yes | yes |
settings:read | yes | no |
analytics:read | yes | no |
verification:delete | yes | no |
applicant:read | no | no |
verification:reopen | no | no |
settings:write | no | no |
console:session | no | no |
Two consequences are worth internalising now rather than meeting them in production.
Reading your flows, your merchant profile, your entitlements and your usage works with a sandbox
key and returns 403 with a default live key, because those four need settings:read or
analytics:read. If a call that works in staging fails in production, check this before anything
else.
A decision we have already given you is final unless you hold verification:reopen. Approving or
rejecting an OPEN case needs nothing beyond verification:create, but a case that already carries a
decision will not move: the write is refused whoever sends it, including our own staff. The one way
back is decision: "review" on Decide a manual review, which returns the
case to the queue and clears its verdict, and that verb needs verification:reopen, which is in no
default set. Ask us to put it on a specific key if you need it.
applicant:read is in no default set at all, and it is the one you are most likely to want. It
is the only scope that returns a real person's name, date of birth, document number and the images
of their document, so it is never inherited by a key you made for something else. You grant it
yourself, when you create the key: choose Full data under "What this key can read" in the
dashboard's create-key dialog, or name the scope in the permissions array on POST /v1/api-keys.
An existing key cannot gain it, so this is a new key rather than a change to the one you are holding.
See Applicant data.
A live key can only be created in the dashboard. POST /v1/api-keys needs settings:write,
which no live key may carry, and a sandbox key holding it may only create keys in its own
environment. So the endpoint is how you script sandbox keys, and a person in the console is the only
way a live one comes into existence. That is deliberate.
console:session is in no default set either, and unlike applicant:read you cannot grant it to
yourself. It is the only scope that mints a credential acting as one of your people rather than
reading your data, and it needs a seat mapped to it before it resolves to anybody. Ask us for it on
one key. See Back-office sessions.
Create narrow keys per integration rather than one key for everything, and revoke the narrow ones freely.
Read-only keys
A key is created either read and write or read only, and that is a separate question from
which scopes it carries. A read-only key reads everything it is scoped for and is refused every
write with 403 and the code READ_ONLY_KEY.
Choose it in the dashboard when you create the key, or send "access": "read_only" to
POST /v1/api-keys. You can read it back from GET /v1/entitlements, which is the cheap way to
assert in your own tests that a job is holding the credential you think it is.
It is not a permission, and that is the whole value. Scopes are additive: we add new ones over time, and a key you called read-only because it happened to hold no write scopes would quietly gain the ability to write the day somebody granted it a scope that did not exist when you made that judgement. Read-only is a property of the credential instead. Granting a write scope to a read-only key changes nothing, so a key you created read-only in January is still read-only in December without you re-auditing it.
It is fixed for the life of the key. There is no way to widen one, in the dashboard or over the API, and there will not be. A read-only key that could be flipped is a read-write key with a setting, which is not something you could hand to a contractor and stop thinking about. To widen, create a new key.
Reach for one whenever something needs to read and never to write: a nightly pull into your warehouse, an internal dashboard, a support tool your agents use, a report. The common shape is two keys, one read-only for the data and one read-write for the calls that actually create verifications.
Read-only says nothing about how much you can read. The two questions are independent, and a read-only key holding the default scopes reads only what those scopes reach. The create dialog asks both: What this key can do is read-only or read and write, What this key can read is outcomes only or full data. A credential that mirrors your account and changes none of it is the pair read-only plus full data, which is what to point a nightly sync, a warehouse pull or an internal dashboard at.
The one read scope you cannot grant yourself is console:session, because it does not read your data,
it mints a credential that ACTS AS one of your people. Ask us for that one.
One deliberate exception to the shape of the rule: POST /v1/verifications/lookup is allowed on a
read-only key. It uses POST because its filters outgrew a query string, but it is a search and
changes nothing. We classify every endpoint by what it does rather than by its HTTP verb.
Request correlation
Every response carries an X-Request-Id header. You can also send your own X-Request-Id and it
will be echoed back. Log it. It is the fastest way for support to find a specific request, and it is
included in every error body as error.request_id.