Authentication
Every call to Retorna Gateway carries two independent layers. This page covers the first one; the second has its own guide.
| Layer | What it proves | Where |
|---|---|---|
| Application | Your access token | this page |
| Integrity | Your request signature | Request signing |
Getting a tokenβ
POST /oauth2/token with the OAuth2 client_credentials grant. Your
clientId / clientSecret pair travels in an Authorization: Basic header, and
the scope in a form-encoded body:
curl -X POST https://api.gateway.sandbox.retorna.app/oauth2/token \
-H "Authorization: Basic $(printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "scope=sandbox/full_access"
The scope is fixed per environment: sandbox/full_access in sandbox and
prod/full_access in production. There is no finer-grained scope β the
credential pair already defines what your company can do.
{
"access_token": "eyJraWQiOiJ...",
"expires_in": 3600,
"token_type": "Bearer"
}
Then send it on every subsequent call:
Authorization: Bearer eyJraWQiOiJ...
What this is notβ
This is machine-to-machine authentication, not a user login. There is no
email, no password, no idToken, and no refresh token β when the token
expires you request another one the same way. Treat the credential pair as an
API key: it identifies your company, not a person.
That is also why no endpoint takes an account, owner, or company parameter.
Your company is resolved from the verified token, so GET /me/wallets/{currency}
and GET /me/routes take no company parameter to know whose wallet and whose
corridors to return. One credential, one company.
Token lifetimeβ
expires_in is seconds β 3600 in the example above, but read the field
instead of hardcoding an hour. Cache the token and reuse it until it is close to
expiry rather than requesting one per call.
When it failsβ
A 401 on /oauth2/token covers every credential problem: missing
Authorization header, a scheme other than Basic, base64 that does not decode
to clientId:clientSecret, wrong secret, or a scope your credentials were not
provisioned for.
A 401 on any other endpoint means the token itself was rejected β absent,
malformed, expired, or not valid for the channel. The body distinguishes the two
cases:
{ "message": "Bearer token required", "error": "Unauthorized", "statusCode": 401 }
{ "message": "Invalid api token", "error": "Unauthorized", "statusCode": 401 }
A valid token is not enough on its own: most endpoints also require a signature.
If the token is fine and you still get a 401, read
request signing β the two failures look identical from
the outside.