Skip to main content
Every authenticated /v1 request requires both:
  1. An OAuth 2.0 access token in the Authorization header (scheme: DPoP, not Bearer).
  2. A DPoP proof JWT in the DPoP header — signed fresh per request with a key only your merchant holds.
Without DPoP, a leaked access token is full account access. With DPoP, a leaked token alone is inert — the proof cryptographically binds the request to the holder of the matching private key. Anton accepts two credential types on the same /v1 routes:
  • OAuth access tokens (DPoP-bound) — for server-to-server integrations.
  • WorkOS portal JWTs — issued to users signed in to the merchant dashboard.
This page covers programmatic OAuth auth. Portal JWT auth is automatic from the dashboard and not something integrators need to wire by hand.

Glossary

Generating credentials

Sign in to the merchant portal, go to Settings → API Credentials, and click Create credentials. The portal:
  1. Generates an ES256 (P-256) DPoP keypair in your browser using WebCrypto. The private key never leaves your device.
  2. Submits the public JWK to Anton.
  3. Returns client_id, client_secret, and the private key in PEM + JWK form — all three shown once. Copy them or download the .pem.
Store the secret + private key in your secrets manager. Pass them to your service via env vars or a secrets mount. Never check them in. Each credential carries a scope set (payouts, intelligence) fixed at creation time. Unless you narrow it, a new credential defaults to your account’s full capability set. Scopes are immutable after creation — create a new credential and revoke the old one to change them. Calls outside a credential’s scope fail with 403 insufficient_scope; calls outside your account’s capabilities fail with 403 capability_required. See Capabilities & Scopes. Sandbox credentials are issued by app.antonpayments.dev and only authenticate against api.antonpayments.dev. Production credentials similarly only work against api.antonpayments.com.

Token endpoint

POST /oauth/token exchanges your (client_id, client_secret) plus a DPoP proof for an access token. Request:
  • Content-Type: application/x-www-form-urlencoded
  • Authorization: Basic <base64(client_id:client_secret)> (body fallback also accepted: client_id + client_secret as form fields)
  • DPoP: <proof JWT> — required, with htm=POST, htu=https://api.antonpayments.dev/oauth/token, fresh iat and jti, and no ath claim.
  • Body: grant_type=client_credentials
Response (200):
expires_in is in seconds. Cache the token until exp - 60s then re-mint. There are no refresh tokens — call the endpoint again with your secret. scope echoes the credential’s grants, space-delimited (RFC 6749 §3.3) — the same values are embedded in the access token’s scope claim. There is no scope request parameter: tokens always carry exactly the scopes the OAuth client was created with. See Capabilities & Scopes. Errors (RFC 6749 §5.2 envelope):

Making authenticated requests

Every /v1 call needs both headers:
The proof MUST carry these claims:
  • htm — HTTP method (POST, GET, etc.)
  • htu — request URL, scheme + host + path only (no query, no fragment)
  • iat — Unix timestamp, must be within ±60 seconds of Anton’s clock
  • jti — unique identifier; Anton rejects replays within a 5-minute window
  • ath — base64url(SHA-256(access_token))
Header (the embedded jwk MUST hash to the jkt you registered):

curl quickstart

The script below mints a token and calls GET /v1/beneficiaries against sandbox. It shells out to openssl, jq, and a small Python helper for ECDSA signature conversion — adapt to your language as needed; production clients should use a JOSE library rather than this verbose shell version.

Token caching

  • Cache an access token until exp - 60 seconds. Don’t refresh on every call — it’s wasted round-trips.
  • On 401 invalid_token, force-refresh once (the previous token may have expired between mint and use).
  • DPoP proofs are not cacheable — generate a fresh one for every request.

Troubleshooting

Security model

DPoP defends against bearer-token theft. A leaked access token alone is useless without the matching private DPoP key. This neutralises a common class of incidents — tokens captured via stack traces, error reports, network proxies, or misconfigured logging. DPoP does NOT defend against full credential-store theft. If an attacker exfiltrates both the private DPoP key AND a live access token from your infrastructure, they can forge proofs and act as you for up to one token TTL. The mitigations are short token TTLs (1 hour in production), key rotation discipline, and reducing blast radius via the credential’s revoke endpoint. Token TTL is the revocation mechanism. If you suspect a credential is compromised, rotate the client_secret (the rotate endpoint issues a new one) and revoke the client. Outstanding tokens will continue to verify until their exp — at most 1 hour in production. For emergency revocation of in-flight tokens, contact support. Anton’s signing key rotates every 90 days with a 24-hour publication overlap. The JWKS endpoint at /v1/.well-known/jwks.json always reflects the currently-trusted set; cache it for ≤5 minutes (the response includes a matching Cache-Control).

SDKs

SDKs are not part of v1. Programmatic clients integrate against the raw HTTP surface; the curl example above is the canonical reference. Official SDKs for Go, Node, and Python are tracked as a follow-up initiative — when they ship, the canonical authentication examples will move into the per-language quickstarts.

Requirements

  • Use HTTPS. Anton only accepts TLS 1.2+ connections. Plaintext requests are dropped at the edge.
  • Never commit credentials to source control. Use environment variables or a dedicated secrets manager. The DPoP private key especially — it’s the second half of your bearer-token defense.
  • Never include credentials in frontend code. All API calls must originate from a trusted backend.
  • Prefer one credential per service. Narrower blast radius when a credential needs to be rotated.
  • Wall clock within 60 seconds of UTC. NTP in your container/host. DPoP iat claims are time-bounded.