Developer Auth
Signup, login, sessions, token refresh, logout, OAuth grants, and current-developer lookup.
Developer credentials are owned by Amba's Amba API. Passwords are hashed with bcrypt (10-round salt). Refresh tokens are stored as sha256 hashes so a DB dump can't replay them.
Sessions
Every sign-in (signup, login, emailed code, GitHub) starts a session and returns its access token (24 hours) and refresh token (30 days). An access token works only while its session is live:
POST /auth/developer/logoutends the session. Its refresh token and every access token it issued stop working within 30 seconds.POST /auth/developer/refreshreplaces the refresh token. Access tokens issued earlier in the same session keep working until they expire.- A refresh token presented again after it was replaced ends the whole
session (
401 REFRESH_TOKEN_REUSED), for whoever copied it and for the client that refreshed. Two processes sharing one refresh token must refresh one at a time; the CLI does this with a lock file. - An access token without a session (issued before 2026-10-15) answers
401 INVALID_DEVELOPER_TOKEN. Refresh it, or sign in again.
Rate limits
Per-IP, two-tier (fast + daily):
| Route | Per minute | Per day |
|---|---|---|
POST /auth/developer/signup | 5 | 50 |
POST /auth/developer/waitlist | 5 | 50 |
POST /auth/developer/login | 10 | 100 |
POST /auth/developer/otp/request | 5 | 50 |
POST /auth/developer/otp/verify | 20 | 200 |
POST /auth/developer/refresh | 30 | — |
otp/request is additionally capped per address (5 per hour, 20 per day). /logout and /me are not rate-limited beyond the global gateway defaults.
POST /auth/developer/signup
Create a new developer and return an access + refresh token pair.
Signup is open, so invite_code is optional. If signups become
invite-only, a call without invite_code answers
403 INVITE_REQUIRED. See Getting access for the signup
modes and how to request a code.
Request
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Unique email address. |
password | string | yes | Minimum 8 characters. Bcrypt-hashed server-side. |
name | string | no | Display name. |
invite_code | string | if invite-only | Invite code from the invite email (AMBA- plus four groups of four letters and digits, such as AMBA-7KQ2-M9XD-4TNB-HR3W; case, spaces, and dashes are ignored). Optional when signups are open. A code that is sent must be valid in every mode. |
Response 201
Errors
400 INVALID_INPUT— missing or malformed email, or missing password.400 WEAK_PASSWORD— password is shorter than 8 characters.400 DISPOSABLE_EMAIL— the address is on a disposable / temporary inbox domain. Use a permanent address.403 INVITE_REQUIRED— signups are invite-only and noinvite_codewas sent.403 INVITE_INVALID— theinvite_codeis mistyped, expired, revoked, or already used.403 SIGNUPS_CLOSED— new accounts are closed for now; invite codes are refused too.409 EMAIL_EXISTS— the email, or an alias of the same mailbox, is already registered. Aliases are matched case-insensitively with any+tagremoved, and with dots ignored for Gmail addresses (j.doe+work@gmail.comandjdoe@gmail.comare the same mailbox). A sent invite code stays unspent.429— rate limited.500 CREATE_FAILED— database error. Inspect server logs.
The three 403 refusals carry where to request access:
Try it:
/auth/developer/signupcurl -X POST 'https://api.amba.dev/v1/auth/developer/signup' \
-H 'Content-Type: application/json' \
-d '{
"email": "you@example.com",
"password": "example-password"
}'Curl:
POST /auth/developer/redeem-invite
For an account that already exists: redeems an invite code so the account can
create projects while access is limited. New accounts pass invite_code at
signup instead. Requires the developer Bearer (session token or PAT).
Response 200: { "data": { "can_provision": true, "redeemed": true } }. An
account that can already create projects gets "redeemed": false and the code
stays unspent.
Errors: 403 INVITE_INVALID (mistyped, expired, revoked, or already used),
403 SIGNUPS_CLOSED (codes are refused while signups are closed),
403 PROVISIONING_REVOKED (project creation was turned off for the account;
contact support), 401 without a valid Bearer, 429 when rate-limited (10
per minute per IP).
POST /auth/developer/waitlist
Request access when signups are invite-only or closed. Public, no authentication. When access is granted, Amba emails the address an invite code, a signup link with the code filled in, and a one-line prompt to paste into a coding agent. The console form at app.amba.dev/request-access calls this endpoint.
Request
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Where the invite code is sent. |
note | string | no | What you're building. The first 500 characters are kept. |
Response 200
Every well-formed email gets this same response, whether or not the address is already listed or already has an account, so the endpoint cannot be used to enumerate accounts. Asking again for the same address is safe; the note from the first request is the one kept.
Errors
400 INVALID_INPUT— body is not JSON,emailis missing or implausible, ornoteis not a string.429— rate limited (5 per minute, 50 per day per IP).429 WAITLIST_FULL— the day's list of new addresses is full. Try again tomorrow.503 WAITLIST_UNAVAILABLE— the request was not recorded. Try again in a few minutes.
Curl:
POST /auth/developer/login
Exchange email + password for tokens.
Request
| Field | Type | Required |
|---|---|---|
email | string | yes |
password | string | yes |
Response 200
Errors
400 INVALID_INPUT— missing email or password.401 INVALID_CREDENTIALS— wrong email or password. (Generic on purpose — never leak whether the email exists.)429 RATE_LIMIT_EXCEEDED— too many attempts from your address (10/min, 100/day), or too many failed password attempts for this account (20/hour, 100/day, counted across every Amba sign-in surface).Retry-Aftersays when to retry; an emailed sign-in code still works in the meantime.500 LOGIN_FAILED.
Try it:
/auth/developer/logincurl -X POST 'https://api.amba.dev/v1/auth/developer/login' \
-H 'Content-Type: application/json' \
-d '{
"email": "you@example.com",
"password": "example-password"
}'Curl:
POST /auth/developer/otp/request
Email a 6-digit sign-in code to an existing developer account. This is the passwordless path: an account that never set a password (for example one created by an agent and later bound to your address) signs in with the code instead. The console's Email me a sign-in code option and amba login use it.
Request
| Field | Type | Required |
|---|---|---|
email | string | yes |
Response 200
The response is the same whether or not an account exists for the address, so it cannot be used to enumerate accounts. Codes expire after 10 minutes, are single-use, lock after 5 wrong attempts, and a new request replaces any code still outstanding.
Errors
400 INVALID_INPUT— missing or implausible email.429 RATE_LIMIT_EXCEEDED— per-IP or per-address limit hit (Retry-Afteris set).500 OTP_REQUEST_FAILED.
POST /auth/developer/otp/verify
Redeem the emailed code for a session. Returns the same envelope as POST /auth/developer/login, and marks the account's email verified (a sandbox account is promoted to verified_free).
Request
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | The address the code was sent to. |
code | string | yes | The 6 digits from the email (spaces are fine). |
Response 200
Identical to POST /auth/developer/login: access_token, refresh_token, developer.
Errors
400 INVALID_INPUT— missing email, or a code that is not 6 digits.401 OTP_INVALID— wrong, expired, already-used, or locked code, or no account for the address. (Generic on purpose.)429— rate limited.500 OTP_VERIFY_FAILED.
Curl:
POST /auth/developer/refresh
Rotate the refresh token. In one step, the presented refresh token is revoked and a new access + refresh pair is issued in the same session. A refresh token presented after it was already replaced ends the whole session: that is how a copied token is detected, and both the copy and the client that refreshed must sign in again.
Request
| Field | Type | Required |
|---|---|---|
refresh_token | string | yes |
Response 200
Errors
400 INVALID_INPUT.401 INVALID_TOKEN— token signature failed, session not found, expired, or the session already ended.401 REFRESH_TOKEN_REUSED— the token was already replaced; its session has now ended.429— rate limited.500 REFRESH_FAILED.
Try it:
/auth/developer/refreshcurl -X POST 'https://api.amba.dev/v1/auth/developer/refresh' \
-H 'Content-Type: application/json' \
-d '{
"refresh_token": "<refresh_token>"
}'Curl:
POST /auth/developer/logout
End the session the refresh token belongs to: its refresh token and every access token it issued stop working within 30 seconds. Other sign-ins of the same account stay signed in, including a CLI that signed in through the console (amba login gets a session of its own). Idempotent — an already-invalid token returns success. amba logout calls this for the CLI's stored session.
Request
| Field | Type | Required |
|---|---|---|
refresh_token | string | yes |
Response 200
Errors
400 INVALID_INPUT.500 LOGOUT_FAILED.
Try it:
/auth/developer/logoutcurl -X POST 'https://api.amba.dev/v1/auth/developer/logout' \
-H 'Content-Type: application/json' \
-d '{
"refresh_token": "<refresh_token>"
}'Curl:
GET /auth/developer/oauth-grants
List the apps connected to the account through OAuth: each MCP client (Claude, Cursor and others) you approved on the MCP server's sign-in page. The console shows them on the Connected apps page. Authenticate with a session token or the PAT; a connected app's own OAuth token gets 403 OAUTH_TOKEN_CANNOT_MANAGE_GRANTS here and on DELETE (an app ends its own connection with the MCP server's /revoke).
Request
Response 200
client_name is chosen by the app when it registers; treat it as a claim and check redirect_hosts. expires_at is when the connection ends unless the app refreshes before then.
DELETE /auth/developer/oauth-grants/:grantId
Disconnect one app: revoke every refresh token of the grant. The MCP server refuses the app's access tokens within 30 seconds, and the app has to be approved again to reconnect. Replacing the personal access token (POST /auth/developer/pat/rotate) disconnects every app at once and reports how many in oauth_grants_revoked.
Response 200
revoked is false when the grant had already ended.
Errors
404 OAUTH_GRANT_NOT_FOUND— no grant with that id belongs to the caller.
GET /auth/developer/me
Return the current developer profile. Requires a valid access token.
Request
Response 200
Errors
401 UNAUTHORIZED— missing or invalid access token.404 NOT_FOUND— the developer the token references no longer exists.500 FETCH_FAILED.
Try it:
/auth/developer/mecurl -X GET 'https://api.amba.dev/v1/auth/developer/me'Curl: