Quickstart
Three steps, and no special SDK to install.
- Create an API key in the dashboard. It looks like
sk-laju-...and is shown once. - Top up your balance. During the beta, top-ups arrive as a redemption code you apply in the dashboard.
- Point your client at
https://api.lajuapi.comand send a request as usual.
$ curl https://api.lajuapi.com/v1/messages \ -H "x-api-key: $LAJU_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-opus-5", "max_tokens": 256, "messages": [{"role":"user","content":"hello"}] }'
Authentication
One key works in two header styles. Use whichever your SDK sends; both draw on the same account balance.
- Anthropic style:
x-api-key: sk-laju-... - OpenAI style:
Authorization: Bearer sk-laju-...
Keep the key in an environment variable rather than in code that gets committed. A leaked key can be revoked from the dashboard without losing your balance.
Base URL
| Client | Base URL | Note |
|---|---|---|
| Anthropic SDK, Claude Code | https://api.lajuapi.com | The SDK appends /v1/messages itself |
| OpenAI SDK, LangChain | https://api.lajuapi.com/v1 | Include /v1, since the SDK appends /chat/completions |
This host serves the API only. Every path outside /v1/* and /healthz answers 404 in our own error shape. If you get an HTML page back, you are on the wrong host.
Endpoint list
| Method | Path | State | Note |
|---|---|---|---|
| POST | /v1/messages | live | Anthropic format, JSON and SSE |
| POST | /v1/chat/completions | live | OpenAI format, JSON and SSE |
| POST | /v1/responses | live | OpenAI Responses API |
| POST | /v1/images/generations | live | Billed per image |
| GET | /v1/models | live | Needs a key. Used by clients that discover models |
| POST | /v1/messages/count_tokens | not yet | Not served yet. Count tokens client-side for now |
Streaming and tool use
Send stream: true and the SSE frames are forwarded as they arrive, in both formats. Tool definitions pass through untouched, so agentic tools that lean on many tool-call rounds behave normally.
If an upstream breaks mid-stream we do not simply drop the connection. You get a typed error frame carrying a request ID, so your client can tell a finished stream apart from a failed one.
event: error data: {"type":"error","error":{"type":"overloaded_error", "message":"The service is temporarily unavailable. Please retry.", "retryable":true},"request_id":"req_01HQ8Z3f9a"}
Models and IDs
Call a model by the exact ID shown in the price table, for example claude-opus-5 or gpt-5.6-terra. We do not map aliases onto other models, and nothing is substituted when a supplier is busy. An unknown ID returns 404 model_not_found rather than a stand-in.
The live list is always available from GET /v1/models with your key.
Pricing and billing
- Prepaid balance in USD, drawn down as each request completes. No subscription, no card on file.
- Text models are billed per token, input and output separately, at the rates on the pricing page.
- Image models are billed per image. The tier you call fixes the pixel budget, and the size and quality parameters are ignored.
- Balance does not expire. When it runs out the next request is refused with 403
insufficient_quotarather than served and billed later.
During the beta: top-ups are issued by hand as redemption codes. Pay by USDT, QRIS, e-wallet, or bank transfer, and the code lands on your account. Automated top-ups arrive with public signup.
Errors and retries
Every failure is normalised to our own contract. Upstream text is never passed through raw, with one exception: a 400 validation message, which is the only error that tells you how to fix your request.
| HTTP | code | Meaning | Safe to retry |
|---|---|---|---|
| 400 | invalid_request_error | The request was rejected; the message names the part to fix | No, fix it first |
| 401 | invalid_api_key | Key is wrong, revoked, or in the wrong header | No |
| 403 | insufficient_quota | Not enough balance | No, top up first |
| 404 | model_not_found | Unknown model ID | No |
| 429 | rate_limit_exceeded | You are calling too fast | Yes, back off |
| 429 | server_busy | Every route for that model is saturated | Yes |
| 503 | upstream_unavailable | Supplier is down and failover found no healthy route | Yes |
| 504 | api_error | Upstream timed out | Yes |
| 500 | api_error | The request could not be processed | Yes |
The body follows whichever format you called. The OpenAI shape carries code, the Anthropic shape carries error.type. Both carry retryable and request_id.
// OpenAI style {"error":{"message":"The service is temporarily unavailable. Please retry.", "type":"api_error","code":"upstream_unavailable", "param":null,"retryable":true}, "request_id":"req_01HQ8Z3f9a"} // Anthropic style {"type":"error", "error":{"type":"overloaded_error", "message":"The service is temporarily unavailable. Please retry.", "retryable":true}, "request_id":"req_01HQ8Z3f9a"}
Every response carries an X-Request-Id header. Send your own and we use it, so your logs and ours line up. Quote that ID when you report a problem.
Limits worth knowing
Best-effort, no SLA. Several suppliers sit behind each model with automatic failover, but we do not promise a binding uptime figure. For production traffic that cannot stop, keep a fallback to an official provider.
count_tokensis not available yet. Clients that call it first may fail at that step, so count tokens on your side for now.- Beta rates are indicative and track the aggregator market. Balance already bought is honoured at the rate you paid.
- Signup is invite-only until automated top-ups are ready.
Tested clients
Any client that allows a custom base URL works. These are the ones we run ourselves.
Getting help
Request failed and you cannot tell why? Send the request_id, the model ID, and roughly when it happened through the contact page. That ID lets us trace a single request without needing your prompt.
Route health is on the status page, and the figures there are the same ones shown on the home page.
Also available in Bahasa Indonesia.