maat_partner_…), you swap it at POST /v1/token
for a short-lived bearer token that is scoped to exactly one Maat workspace, and you send
that token in the Authorization header on every call. That is the whole model.
The exchange has a reference page of its own now, with the full response shape and every
error it can return. Its 401 and 503 are problem documents like everything else in this
API — see Errors.
https://maatstays.wtf/v1 — the one host this contract is
published against. Whether your requests land on a sandbox or a production workspace is
decided by your key, never by the hostname.
What the response tells you
Read
sandbox at least once, in whatever you build. It is how an integration proves it is
pointed where its author thought it was, and every 501 in this API is explained by it.
The key
- Your key looks like
maat_partner_a1b2c3d4e5f60718_…and is shown once, when it is issued. Store it in a secret manager; we hold only a hash and cannot show it again. - Every key is issued for exactly one workspace — the container a portfolio’s properties and calendars live in — and that workspace is either a sandbox or a production one. Nothing you send can change that — see Sandbox.
- The key opens exactly one door: the token exchange. It never authenticates a contract endpoint directly.
- The exchange takes an empty body, on purpose. The workspace, the environment and your identity all come from the key itself; anything you put in the body is ignored.
The token
- Tokens live for fifteen minutes by default and never more than an hour
(
expires_intells you exactly). Mint one per batch of work, not one per request — the exchange has its own, separate rate ceiling. - Store the key, mint tokens from it. Never persist a token, and never send one as
a cookie or in a query string — the API reads the
Authorizationheader and nothing else. - Revocation does not wait for expiry. We re-check the key behind a token on every request, so revoking a key kills its outstanding tokens on their next call.
Scopes
A key is issued with a fixed list of scopes, and every operation in the API reference names the one scope it requires, asx-maat-scope on the operation. It is checked in CI
against the route table the gateway enforces, so it cannot silently disagree with what your
key needs.
x-maat-scope is the floor. Four operations have a second requirement that depends on
the path rather than on the operation, and the whole vocabulary — all eighteen scopes, a
least-privilege table by job, and what an issuer must hold to grant each one — is
Scopes.
A token minted from a key that lacks the scope is refused with 403 insufficient_scope, and
unlike the 401 this one names the missing scope in detail: it is your own credential,
and the fix is on your side — a key that carries it, which is a new key
(Get access).
- Scopes are fixed when the key is issued. They cannot be widened later; a key that needs more is a new key.
- They are re-read from the key on every request, like revocation. A key narrowed today is narrowed on its next call, before any outstanding token expires.
- The token’s
scopeis what the key held at exchange time, echoed for your own reading — decode it to see what a token was issued for, never to decide what to send. partner:token.issueopens the exchange itself and nothing else. It is on every key; on its own it reaches no operation.
When it is refused
A refused exchange is one indistinguishable401 whether the key is unknown,
revoked, or the secret is wrong — telling those apart would let anyone probe which
credentials exist. If a key that worked yesterday is refused today, the answer is on our
side: see Get access.
The token endpoint is the one place in this API that answers refusals with a plain
{ "message": … } body rather than a problem document. Every other response is
application/problem+json — see Errors.