Authenticate API requests

Calling Moov from your own server? Use Basic authentication with your API key. Calling from a browser or mobile app (Moov.js, Drops)? Mint a short-lived, scoped access token on your server and send only the token to the client.

Both methods start with an API key, which has a public key and a secret key. For the full reference, see authentication options, access tokens, and scopes.

Method Header Use it for
Basic authentication Authorization: Basic <base64(publicKey:secretKey)> Server-side code you control. The secret key never leaves your server.
OAuth2 access token Authorization: Bearer <access_token> Browser and mobile code: Moov.js, Drops, and the client-side TypeScript SDK. Also useful on the server when you want a token limited to specific scopes.

Never put your secret key in a browser, mobile app, or public repository. If code runs on a device you don't control, mint an access token on your server and send only the token to the client.

Basic authentication (server-side)

Set the Authorization header to Basic followed by the Base64 encoding of your public key and secret key joined by a colon. Most HTTP clients do the encoding for you. With curl, use -u:

curl -X GET "https://api.moov.io/accounts/{accountID}" \
  -u "{publicKey}:{secretKey}" \
  -H "X-Moov-Version: v2026.07.00"

Basic authentication uses your secret key directly and isn't narrowed by scopes, which is why it belongs only in server-side code. The Moov server-side SDKs take the same public and secret key pair.

OAuth2 access tokens

An access token is a short-lived JSON Web Token (JWT) that carries a list of scopes, the permissions the holder has. Your server requests the token with your API key, then uses it or hands it to a browser.

Request a token

Call the create access token endpoint from your server with the client_credentials grant and a space-delimited list of scopes. Pass your public key and secret key as the Basic auth username and password (or as client_id and client_secret in the body).

curl -X POST "https://api.moov.io/oauth2/token" \
  -u "{publicKey}:{secretKey}" \
  -H "Origin: https://your-domain.com" \
  -H "X-Moov-Version: v2026.07.00" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "scope=/accounts.write /accounts/{accountID}/profile.read"

The endpoint accepts a form-encoded body, as shown, or a JSON body with the same fields. The response contains the token:

{
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJFZERTQSIsImN0eSI6IkpXVCIsImtpZCI6IkR...",
  "refresh_token": "eyJhbGc0eSI6TQSIsImN0kpXVCIsImtp6IkpXVsImtpZC0a...",
  "expires_in": 1736964352,
  "scope": "/accounts.write /accounts/{accountID}/profile.read"
}

Use the token

Send the token in the Authorization header with the Bearer scheme. Include the same Origin (or Referer) header you used to request it:

curl -X GET "https://api.moov.io/accounts/{accountID}" \
  -H "Authorization: Bearer {access_token}" \
  -H "Origin: https://your-domain.com" \
  -H "X-Moov-Version: v2026.07.00"

In a browser, pass the token to Moov.js or a Drop instead; they set the header for you. See Moov.js for initialization.

The examples in these guides use Authorization: Bearer {token}. If you call the API from your server, you can replace that header with Basic authentication (-u "{publicKey}:{secretKey}") in any example.

Scopes basics

  • Request only the scopes the task needs. A token used to create an account needs /accounts.write; a token used to add a card to that account needs /accounts/{accountID}/cards.write.
  • Most scopes include an account ID, so you need the ID before you request the token. If you're creating an account and then acting on it, request one token for /accounts.write, create the account, then request a second token with scopes for the new account ID.
  • For transfer scopes (/accounts/{accountID}/transfers.write and .read), the account ID is your partner account ID. For all other scopes, it's the user's account ID.
  • Each API reference page lists the scope that endpoint requires.

The scopes reference lists every scope and explains when to generate multiple tokens.

Origin and Referer domains

Requests to POST /oauth2/token, and every API call that uses the resulting token, must include an Origin or Referer header whose domain matches a domain registered on your API key.
  • Register domains in the Dashboard under Developers > API keys when you create the key.
  • Origin is the scheme and domain only, with no path (for example, https://your-domain.com). Referer may include a path.
  • Send only one domain per request: the one the request comes from. If both headers are present, Origin takes precedence.
  • localhost is not an accepted domain. For local development, register and use a tunnel domain (such as ngrok) or a deployed preview domain.
  • Browsers set Origin automatically. Server-side code that requests a token must set it explicitly.

The create access token endpoint also accepts an optional client_type field. It defaults to web; device and service clients don't require browser origin binding.

Token expiry and refresh

Access tokens are short-lived and meant for a single task. The expires_in field in the response tells you when the token expires. When it does, you have two options:

  • Request a new token with grant_type=client_credentials. This is the simplest approach, and lets you change the scopes.
  • Exchange the refresh_token for a new access token. A refreshed token keeps the original client type.
curl -X POST "https://api.moov.io/oauth2/token" \
  -u "{publicKey}:{secretKey}" \
  -H "Origin: https://your-domain.com" \
  -H "X-Moov-Version: v2026.07.00" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token={refresh_token}"

When a user signs out, or you no longer need a token, revoke the access token or refresh token with the revoke access token endpoint.

Test mode and production mode keys

Test mode and production mode each have their own API keys, and the key you authenticate with determines the mode. Both modes use the same base URL, https://api.moov.io. Accounts, sources, transfers, and webhooks created in one mode aren't available in the other, so use IDs from the same mode as your key.

Keep keys in environment variables or a secrets manager, never in source code. When you go live, swap in production keys, register your production domains on them, and replace any hard-coded test account IDs.

Common authentication errors

Status Likely cause Fix
401 The Authorization header is missing, malformed, or the token has expired Check the Basic or Bearer prefix, re-encode publicKey:secretKey, or request a new token
401 The Origin or Referer header is missing, includes a path in Origin, or doesn't match a domain on the API key Send exactly one registered domain, using the same header on the token request and the calls that follow
401 The API key was deleted, or the secret key is wrong Create a new key in the Dashboard; the secret key can't be viewed again after creation
403 The token doesn't include the scope the endpoint requires, or the scope names a different account ID Check the endpoint's required scope in the API reference and request a token with it
422 from POST /oauth2/token Invalid scope or refresh_token value Check scope spelling (scopes start with /) and that the refresh token is current
404 on any request Malformed X-Moov-Version value (for example, 2022), or an ID from the other mode Use v2026.07.00, and confirm the key and the ID are from the same mode

The domain in Origin or Referer is embedded in the access token when it's created, so the header must be on the POST /oauth2/token request itself, not only on later calls. If it's missing or doesn't match a registered domain, every call made with that token fails with a 401.

CORS errors and failed browser requests

If a request from Moov.js, a Drop, or other client-side code fails with a CORS error, check that:

  • The page making the request is served from a secure https domain. localhost isn't treated as secure, even if your browser shows it as https.
  • That domain is on your API key's allowed domain list. See API keys.
  • The access token was created with every scope the request needs, and it's included in the request.

Every response includes an x-request-id header. Include it when you contact support about an authentication problem.

Next steps