Authentication / API keys
GetRatesAPI uses two independent, header-only credential schemes. No cookie or query-string credential is accepted anywhere.
| Scheme | Header | Used for |
|---|---|---|
| Session token | Authorization: Session <token> | Account/API-key/usage/billing management endpoints |
| API key | Authorization: ApiKey <key> | The data plane — rates, conversion, asset catalog — and every hosted MCP tool call |
A session token is never accepted on a data-plane endpoint or by any MCP tool; an API key is never accepted on account-management endpoints.
1. Register
curl -X POST https://your-getratesapi-host/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"a-strong-password"}'await fetch("https://your-getratesapi-host/v1/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "[email protected]", password: "a-strong-password" }),
});Registration sends a verification email. Open the link in it (or paste the code from it on the portal’s verification page) before logging in. No email? Ask for a new one:
curl -X POST https://your-getratesapi-host/v1/auth/verify-email/resend \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'The link is single-use and valid for 24 hours. Completing a password reset also confirms the address, since the reset link arrives by email too.
2. Log in
curl -X POST https://your-getratesapi-host/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"a-strong-password"}'Success 200:
{
"session_token": "gra_sess_0000000000000000000000000000000000000000000",
"expires_at": "2026-10-09T12:00:00Z"
}Until the email address is confirmed, a correct password returns 403
with code email_not_verified and no session:
{
"code": "email_not_verified",
"message": "Verify your email address before signing in. Check your inbox for the verification link.",
"request_id": "…"
}A wrong password is always 401 unauthorized, whether or not the address
is verified.
The session token has a fixed 24-hour expiration. There is no refresh endpoint — log in again once it expires.
If the account has two-factor authentication enabled, login returns a challenge instead of a session:
{
"mfa_required": true,
"mfa_token": "gra_mfa_00000000000000000000000000000000",
"mfa_expires_at": "2026-10-08T12:05:00Z"
}3. Create an API key
Using the session token from step 2:
curl -X POST https://your-getratesapi-host/v1/account/api-keys \
-H "Authorization: Session gra_sess_0000000000000000000000000000000000000000000" \
-H "Content-Type: application/json" \
-d '{"name":"my first key"}'await fetch("https://your-getratesapi-host/v1/account/api-keys", {
method: "POST",
headers: {
Authorization: "Session gra_sess_0000000000000000000000000000000000000000000",
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "my first key" }),
});Success 201:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "my first key",
"key_prefix": "gra_live_00000000",
"created_at": "2026-10-08T12:00:00Z",
"api_key": "gra_live_0000000000000000000000000000000000000000000000000000000000000000"
}api_key is returned exactly once in this response and is never
retrievable again — store it securely immediately. The key format is
gra_live_ followed by 64 lowercase hex characters.
key_prefix is a 17-character value: the gra_live_ prefix plus the
first 8 hexadecimal characters of the key’s own secret portion — it is
derived from the secret, not a shared constant. It is included so you can
visually distinguish keys in a list; it never reveals enough of the
secret to reconstruct or guess the full key.
4. Use the API key
curl https://your-getratesapi-host/v1/rates/EUR/USD \
-H "Authorization: ApiKey gra_live_0000000000000000000000000000000000000000000000000000000000000000"Use the same Authorization: ApiKey <key> header for every
hosted MCP tool call.
Managing API keys
GET /v1/account/api-keys(Session required) — lists all keys for your account, including revoked ones. Secrets are never included in list output — onlyid,name,key_prefix,created_at.DELETE /v1/account/api-keys/{id}(Session required) — revokes a key.204on success. Unknown, foreign, or already-revoked ids return404 not_found.
curl -X DELETE https://your-getratesapi-host/v1/account/api-keys/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Session gra_sess_0000000000000000000000000000000000000000000"Deleting revoked keys
Revoked keys stay in the list until you delete them. Only a revoked key can be deleted; its past usage remains in your usage history (shown as “Deleted key”). Both endpoints take no request body.
POST /v1/account/api-keys/{id}/remove— delete one revoked key.204on success; an active key returns409 key_active(revoke it first).POST /v1/account/api-keys/remove-revoked— delete every revoked key. Returns{"removed": 3}.
curl -X POST https://your-getratesapi-host/v1/account/api-keys/remove-revoked \
-H "Authorization: Session gra_sess_0000000000000000000000000000000000000000000"Rotating a key
POST /v1/account/api-keys/{id}/rotate (Session required) issues a
replacement key and revokes the old one in the same transaction:
curl -X POST https://your-getratesapi-host/v1/account/api-keys/3fa85f64-5717-4562-b3fc-2c963f66afa6/rotate \
-H "Authorization: Session gra_sess_0000000000000000000000000000000000000000000"The 201 response has the same shape as key creation, including the
one-time api_key value. The rotated key keeps its name; the old secret
stops working immediately, so deploy the new one before rotating a key
that is serving live traffic.
Two-factor authentication
TOTP, compatible with any standard authenticator app. All four endpoints require a session.
| Method | Path | Purpose |
|---|---|---|
GET | /v1/account/2fa | Current status |
POST | /v1/account/2fa/enroll | Start enrollment, returns a secret and otpauth:// URI |
POST | /v1/account/2fa/confirm | Prove a code and activate |
POST | /v1/account/2fa/disable | Deactivate; requires your password |
POST | /v1/account/2fa/recovery-codes | Regenerate recovery codes; requires your password |
Enrollment is a two-step flow deliberately: a factor you have not yet
proven you can use is never allowed to lock you out. Only confirm
activates it.
In the dashboard (Security) the otpauth:// URI is shown as a QR code
— scan it with Google Authenticator, Microsoft Authenticator, 1Password,
Authy or any TOTP app — together with the setup key for manual entry
(time-based, 6 digits, 30 seconds, SHA-1). Codes may be typed with or
without the space the app shows: 123456, 123 456 and 123-456 are all
accepted.
Confirmation returns ten single-use recovery codes, shown once. Each can be used in place of a TOTP code exactly once.
Completing a login
When login returns mfa_required, exchange the challenge token and a code
for a session:
curl -X POST https://your-getratesapi-host/v1/auth/login/mfa \
-H "Content-Type: application/json" \
-d '{"mfa_token":"gra_mfa_00000000000000000000000000000000","code":"123456"}'The challenge token is valid for five minutes and is single-use. It is not a session: it authorises nothing except completing this login.
Changing your password
POST /v1/account/password with {"current_password":"…","new_password":"…"}.
A successful change revokes every session on the account, including the one that made the request. That is intentional: a password change is what you do when you suspect a session is in the wrong hands, and it would be useless if it left those sessions alive. Log in again afterwards.
Federated sign-in
Google and GitHub sign-in, when the operator has configured them.
GET /v1/auth/oauth/providers reports what is available:
{ "providers": ["google", "github"] }The flow is authorization-code with PKCE where the provider supports it:
- Generate a random binding value (32–128 base64url characters), keep it
in the browser tab, and send the browser to
GET /v1/auth/oauth/{provider}/start?binding=…. - The provider returns to
/v1/auth/oauth/{provider}/callback, which redirects to the portal at/signin?oauth_code=…. - The app exchanges that code:
POST /v1/auth/oauth/exchangewith{"code":"gra_oauth_…","binding":"…"}returns a normal session token — or, when the user has two-factor authentication enabled, the same MFA challenge as a password login, completed at/v1/auth/login/mfa.
The code only works together with the binding from the browser that started the flow, so a code planted in or copied from another browser cannot sign anyone in.
A session token never travels in a redirect URL. The handoff code is single-use and expires in two minutes, so a leaked referrer or browser history entry does not hand over an account.
A provider identity is attached to an existing local account only when the provider asserts the email is verified. Otherwise a new account is created. This prevents anyone who can register an unverified address at a provider from seizing a local account that happens to share it.
If the matching local account never verified its address, the provider’s verified assertion wins: the account’s password, sessions, API keys, two-factor setup and other linked identities are revoked before it is attached, because whoever registered it never proved they own the address.
GET /v1/account/identities lists linked providers;
DELETE /v1/account/identities/{provider} unlinks one. Unlinking is
refused if it would leave you with no way to sign in.
Email delivery
Verification, password-reset, and quota-notification emails are sent
as HTML with a plain-text alternative, through the operator’s configured
transport (MAIL_TRANSPORT). In local
development the default writes messages to disk instead of sending them —
see the repository README.
Known gaps
There is no team/multi-account selector: an account has a single owner.