API overview
One gateway, three compatible dialects. Point an OpenAI, Anthropic or Gemini client at Asale, change the base URL and the key, and the rest of your code stays as it is.
Asale's gateway answers in three wire formats. Whichever one your client already speaks, you keep your SDK, your request bodies and your streaming code — you change two lines: the base URL and the key.
Everything on this page is the same for all three. The per-dialect pages carry the endpoints and the runnable samples:
- OpenAI-compatible —
chat/completions,responses - Anthropic-compatible —
messages - Gemini-compatible —
generateContent - Wallet balance API — check balances, recent spending, and key usage with an API key
Base URLs
Each dialect wants its base URL cut at a different depth, because each vendor's client appends a different suffix. Use the value for the SDK you are holding.
| Dialect | Base URL | Endpoint it calls |
|---|---|---|
| OpenAI | https://gw.asale.ai/v1 | POST /v1/chat/completions |
| Anthropic | https://gw.asale.ai | POST /v1/messages |
| Gemini | https://gw.asale.ai | POST /v1beta/models/{model}:generateContent |
gw.asale.ai is the inference gateway. api.asale.ai is the console API — sessions, wallet, market data — and generally does not accept sk-asale- keys; sending inference there returns 404. The exception is the wallet balance API, which authenticates by key alone.
Authentication
Three headers, one per dialect, so each vendor's SDK authenticates unmodified. Any of them is accepted on any endpoint.
Authorization: Bearer sk-asale-...
x-api-key: sk-asale-...
x-goog-api-key: sk-asale-...
Keys are managed on the API keys page: every account gets one at sign-up, and you can add more, name them, give them an expiry, disable one without deleting it, and read any of them back when you lose the copy.
One key per account is marked default. That is the key the desktop app hands to the AI tools it is buying through — it has no effect on requests you make yourself, which are authenticated by whichever key you send.
Models
Model names are the platform's, not a vendor's SKU list. Ask the gateway what it will match:
curl https://gw.asale.ai/v1/models -H "Authorization: Bearer $ASALE_API_KEY"
{
"object": "list",
"data": [
{ "id": "claude-sonnet-4-5", "object": "model", "owned_by": "anthropic", "context_length": 200000 }
]
}
A model that is in the catalog but has nobody selling it right now answers 503 no_supply rather than 404 — the name is valid, the supply is not there this second. The market page shows what is liquid.
Streaming
Server-sent events, in each dialect's own event shape, with no re-framing: stream: true for OpenAI and Anthropic, :streamGenerateContent for Gemini. Your existing stream parser works unchanged.
What a request costs
Before a request goes out, the gateway holds an estimate — your declared max_tokens at the model's output price. When the response finishes, it is priced on real usage and the difference returns to your available balance immediately.
Nothing is charged when the request errors, when usage comes back zero, or when you disconnect mid-stream. See metering for how tokens are counted.
Errors
Every failure is JSON with the same shape, at the matching HTTP status:
{
"error": {
"message": "insufficient balance",
"code": "payment_required",
"key": "errors.wallet.insufficientBalance"
}
}
Branch on code, never on message — the prose is localized and reworded freely.
| Status | code | What to do |
|---|---|---|
| 401 | unauthorized | The key is missing, malformed or unknown. Check the header and the key. |
| 403 | forbidden | The key is disabled, or the account is blocked from trading. |
| 402 | payment_required | Top up the wallet. |
| 429 | rate_limited | Back off; Retry-After says for how long. |
| 503 | no_supply | Nobody is selling that model right now. Retry, or pick another model. |
| 502 | upstream | The provider that served the request failed. Safe to retry. |
An expired key answers 401 with errors.apiKey.expired; a disabled one answers 403 with errors.apiKey.disabled. The two have different fixes, which is why they are different statuses.
Rate limits
Each key carries its own requests-per-minute and tokens-per-minute ceiling. Exceeding either returns 429 with Retry-After. These are per key, so splitting a workload across two keys splits the limits with it.
Checking your balance
For the complete reference, see the wallet balance API.
Everything else on this page is gw.asale.ai. This one is api.asale.ai, and it is the one console endpoint that authenticates by key alone rather than by a logged-in session — because "what do I have left, and what has this key spent" is a question a script holding only a key should be able to ask without borrowing somebody's login.
GET https://api.asale.ai/api/v1/me/wallet/balance
Authorization: Bearer sk-asale-...
x-api-key and x-goog-api-key work here too, same as on the gateway.
{
"balance": 4820000,
"frozen": 150000,
"payable": 0,
"debt": 0,
"unit": "micro_usdt",
"recent_consumption": [
{ "task_id": "t_...", "model": "claude-sonnet-4-5", "amount_usdt": 1200, "created_ts": 1760000000 }
],
"recent_consumption_window_hours": 24,
"api_key": {
"id": 42,
"label": "ci-bot",
"rpm_limit": 60,
"tpm_limit": 100000,
"max_ratio_pct": 80,
"budget_usdt": null,
"budget_remaining_usdt": null,
"spent_usdt": 0,
"calls": 318,
"in_tokens": 905120,
"out_tokens": 221004,
"settled_usdt": 184230,
"list_usdt": 240310
}
}
balance/frozen/payable/debtare the same numbers as the wallet page, in micro-USDT.recent_consumptionis this account's billed requests from the last 24 hours, capped at 200 rows — not filtered to this one key, so it matches what paging through the account's own history for that window would show.api_keyis this key's own standing: the rate limits set on it, its spend ceiling if it has one, and its all-time totals across every request it has ever paid for.
This is a snapshot for a script, not a paged feed — it answers "where do things stand right now", not "list my last N calls". An unknown, expired or disabled key answers the same way it does on the gateway: 401/403 with the matching code from the errors table above.