Quickstart and API reference
GenAff is an OpenAI-compatible gateway. The web app manages your session; your own code uses a GenAff key on a separate plane. The two credentials are never mixed.
Contract 1.1.1 · Phase 1
Two planes
| Plane | Path | Credential | Used by |
|---|---|---|---|
| Dashboard | /api/v1/* | Bearer <session JWT> | This web app |
| Gateway | /v1/* | Bearer gk_live_… | Your server or SDK |
Common 401s
Gateway key on the dashboard plane: 401 UNAUTHORIZED — “The access token is invalid or has expired.”
Dashboard JWT on the gateway plane: 401 INVALID_API_KEY — “The API key is invalid, expired or revoked.”
These are different credentials with different lifetimes. Do not paste a gk_live_ key into this dashboard.
Quickstart
1. Create an account and verify your email.
The verification token arrives in the URL fragment. This page clears it from the address bar after reading it. You cannot sign in until the email is verified.
2. Connect a provider.
Paste the provider's own API key. GenAff validates it with the provider before storing it encrypted. One connection per provider; use Replace to change it.
3. Create a GenAff gateway key.
The plaintext gk_live_… is shown once. Store it on your server; this dashboard never receives it again.
4. List the models available to you.
curl / models export GENAFF_API_KEY="gk_live_YOUR_KEY" curl https://api.genaff.cloud/v1/models \ -H "Authorization: Bearer $GENAFF_API_KEY"5. Make a non-streaming chat request.
Copy a model ID from the Models page. Phase 1 rejects stream: true and unsupported fields; nothing is silently dropped.
curl / chat/completions curl https://api.genaff.cloud/v1/chat/completions \ -H "Authorization: Bearer $GENAFF_API_KEY" \ -H "content-type: application/json" \ -d '{ "model": "provider:upstream-model", "messages": [{"role": "user", "content": "What is a merkle tree?"}], "max_tokens": 128, "stream": false }'
Providers
Only providers with an adapter and passing contract tests are listed. The landing page reads the enabled set from the public provider endpoint; the dashboard reads the authenticated set. Capabilities are an open map — do not hardcode the full key set.
One credential per provider
Model IDs
IDs use provider_code:upstream_model_id. Only the first colon is a delimiter. Inside the upstream part, these characters are escaped so the ID is URL-path safe:
| Character | Encoding |
|---|---|
| ~ | ~~ |
| : | ~C |
| % | ~P |
| ? | ~Q |
| # | ~H |
| space | ~S |
Slashes and dots are preserved. Never construct an ID yourself; copy it from the Models page or /v1/models.
Chat
Messages accept system, user and assistant roles with string content. Supported fields include temperature, top_p, one of max_tokens or max_completion_tokens, stop and n=1. Tools, response_format, logprobs, penalties, seed, user and images are rejected.
No streaming
Rate limits
Every bucket returns 429 RATE_LIMITED with a Retry-After header. The backend sends seconds; the web client also accepts an HTTP-date. Do not retry in a tight loop.
| Action | Bucket |
|---|---|
| register | 20/hour per IP |
| verify-email | 60/hour per IP |
| resend verification | 5/hour per IP and email |
| login | 20/5 min per IP · 10/5 min per email |
| refresh | 60/min per IP |
| create key | 20/hour per account |
| GET /v1/models | 120/min per gateway key |
| POST /v1/chat/completions | 60/min per gateway key |
Error reference
Every non-2xx response uses the same envelope. Switch on error.code, never on the message. The request ID is echoed in x-request-id and should be included in support tickets.
| Code | HTTP | What it means |
|---|---|---|
| BAD_REQUEST | 400 | Request rejected The request failed. |
| VALIDATION_ERROR | 422 | Check the highlighted field The request failed. |
| PASSWORD_POLICY_VIOLATION | 400 | Choose a stronger password Use at least 12 characters, avoid common passwords, and do not include your email address. |
| INVALID_VERIFICATION_TOKEN | 400 | Verification link unusable The link may be expired, already used, or malformed. Request a new one. |
| INVALID_CREDENTIALS | 401 | Email or password is incorrect Password recovery is not available in Phase 1. Contact support if you are locked out. |
| UNAUTHORIZED | 401 | Sign in to continue The dashboard uses a short-lived access token. A GenAff API key (gk_live_…) belongs on the gateway plane, not /api/v1. |
| INVALID_REFRESH_TOKEN | 401 | Your session ended Sign in again. The old session cannot be reused. |
| REFRESH_TOKEN_REUSED | 401 | Session revoked for security A rotated refresh token was replayed. Every session in that family was revoked. |
| INVALID_API_KEY | 401 | GenAff API key rejected The gateway uses a GenAff API key (gk_live_…). A dashboard JWT belongs on /api/v1, not /v1. |
| EMAIL_NOT_VERIFIED | 403 | Verify your email first Request a new verification link if the original one expired. |
| ACCOUNT_SUSPENDED | 403 | Account suspended Contact support to resolve the account state. |
| FORBIDDEN | 403 | Request rejected The request failed. |
| NOT_FOUND | 404 | Not found The resource may have been removed, or it belongs to another account. |
| MODEL_NOT_FOUND | 404 | Model unavailable Refresh the connection's models or choose a model from the current list. |
| METHOD_NOT_ALLOWED | 405 | Request rejected The request failed. |
| CONNECTION_EXISTS | 409 | Provider already connected GenAff allows one connection per provider. Replace the existing credential instead. |
| PROVIDER_UNSUPPORTED | 400 | Provider not available Choose a provider from the supported list. |
| PROVIDER_CREDENTIAL_INVALID | 400 | Provider rejected this credential Check the key with the provider, then paste it again. Nothing was stored. |
| PROVIDER_UNAVAILABLE | 502 | Provider unavailable Nothing was stored or changed. Try again in a moment. |
| UNSUPPORTED_PARAMETER | 400 | Unsupported parameter Phase 1 accepts a bounded OpenAI-compatible subset. Remove the named field. |
| UPSTREAM_AUTH_FAILED | 502 | Provider credential was rejected Re-check or replace the stored credential for this provider. |
| UPSTREAM_INVALID_REQUEST | 400 | Provider rejected the request Adjust the request parameters. GenAff does not silently drop unsupported fields. |
| UPSTREAM_QUOTA_EXCEEDED | 402 | Provider quota exhausted Your upstream provider account has no credit. GenAff does not bill you for inference — top up with that provider, then retry. |
| UPSTREAM_RATE_LIMIT | 429 | Provider rate limit Wait for the countdown, then retry. Do not send a burst of requests. |
| UPSTREAM_TIMEOUT | 504 | Provider timed out The provider was too slow. Retry only when you intend to make another request. |
| UPSTREAM_ERROR | 502 | Provider error The upstream provider failed. Retry later. |
| UPSTREAM_INVALID_RESPONSE | 502 | Provider returned unusable data Retry once, then contact support with the request reference if it continues. |
| PAYLOAD_TOO_LARGE | 413 | Request too large Reduce the request body. GenAff accepts at most 1 MiB. |
| UNSUPPORTED_MEDIA_TYPE | 415 | Request rejected The request failed. |
| RATE_LIMITED | 429 | Too many requests Wait for the countdown before trying again. |
| INTERNAL_ERROR | 500 | GenAff had a problem Try again. If it continues, contact support with the request reference. |
| SERVICE_UNAVAILABLE | 503 | GenAff had a problem Try again. If it continues, contact support with the request reference. |
Phase 1 boundary
- No managed inference, credits or billing. Your provider account pays for usage.
- No streaming, tool calls, structured output or image input.
- No password reset. Contact support for recovery.
- One connection per provider; replace, do not add.
- No background model refresh; use the explicit Refresh models action.
Ready to try it? Create an account or sign in.