Two tokens, one integration
You are one customer with one contract. But your integration has up to two moving parts, and they authenticate differently — which is the single thing new integrators most often get wrong.
| Your backend | Your app | |
|---|---|---|
| Credential | machine token (client_credentials) | the wallet holder's own token |
| Obtained by | your server, from your client id and secret | the person signing in via /auth/login, or your backend via /auth/delegated-token |
| Acts as | your tenant | that one person |
There is one API reference covering both, and every operation states the token it takes at the top of its description. You never have to work out which section an operation lives in — most people arrive from search anyway.
If you want a spec scoped to one half — to import into Postman, or to generate a client — download
wallet-backend-api.yaml or wallet-app-api.yaml instead of the full wallet-api.yaml.
If your users log in to you, not to us
The table above assumes the wallet holder signs in through us. Most integrators with an existing product do not want that — their users already have an account, and a second login is a worse experience and a support burden.
For that case your backend mints the user token itself:
POST /auth/delegated-token
Authorization: Bearer <your machine token>
{ "externalUserRef": "drv-7001" }
The result is an ordinary user token — /me/* works exactly as it would after /auth/login — with
two extra claims that record where the authentication came from:
{ "amr": ["tenant_delegated"], "act": { "sub": "cli_8a91e0e8..." } }
amr says you authenticated this person; act names the client that asserted them. Neither
changes what the token can do. Both exist so that a dispute, years later, can tell "GoHubPay
verified a password" from "the tenant vouched for their user" — from the token alone.
There is no refresh token: mint another when it expires. A refresh would let the session outlive your ability to re-assert the user, which is authority you have not given us.
See the walkthrough for the full sequence.
Why a machine token cannot do everything
Operations under /me/* resolve the caller from the token and act only as that person. There is
no userId parameter to pass, which is exactly what makes them safe to ship inside a mobile app —
and also why a machine token has nothing to resolve to. It is rejected with 401:
"Authenticated principal is not a wallet user."
This is not a permission you can be granted. It is structural.
The one case that behaves differently by caller
GET /transactions and the two transaction reads are callable with either token, and return
different things:
- with your machine token — transactions across your whole tenant
- with a user token — only that person's own activity
The narrowing happens on our side; you cannot widen it by passing different parameters. If you proxy this endpoint from your app, be deliberate about which token you forward.
If you never mint a delegated token, your users hold no session and no operation marked User
token applies to you — roughly a third of the reference is not part of your integration,
including the whole /me/* surface.
That is a choice, not a limit. Minting a delegated token gets any registered user an ordinary session and the full surface with it. See How the integration works.