Ditfa Platform API
One OpenAI-compatible endpoint for every model in the catalog. Bring any OpenAI SDK, use an sk-dp- key, and pay per token from prepaid credits. No subscription, no usage windows.
Base URL
https://gateway.ditfa.com/v1- Compatibility
- OpenAI Chat Completions
- Any OpenAI SDK or HTTP client
- Auth
- Bearer sk-dp-…
- Per-key limits, models, IPs, expiry
- Billing
- Per token, in USD
- Prepaid credits that never expire
- Routing
- Healthy providers first
- Automatic fallback, up to 3 tries
Get started
Quickstart
- 1Add credits to your wallet ($5 minimum).
- 2Create an API key. It starts with
sk-dp-and is shown only once; store it asDITFA_API_KEY. - 3Point any OpenAI SDK at
https://gateway.ditfa.com/v1and pick a model slug from /models.
curl https://gateway.ditfa.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DITFA_API_KEY" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{
"role": "user",
"content": "Write a haiku about the ocean."
}
]
}'Authentication
Every endpoint except GET /models needs a key, sent as a bearer token:
Authorization: Bearer sk-dp-…Keys are stored hashed; the full key is shown once when you create it. Each key can be scoped in the dashboard:
| Setting | Effect |
|---|---|
| Spend limit | Per day, week, month or in total. Windows are UTC; weeks start on Monday. Requests over the limit get 402 key_limit_reached. |
| Allowed models | Only these slugs can be called. Empty means every published model. |
| IP allowlist | Exact IPv4/IPv6 addresses. Empty means any IP. |
| Expiry date | After it, the key gets 401 key_expired. |
| Provider policy | Verified providers only (no marketplace sellers) and/or no providers that log prompts. Also settable for the whole account. |
Disable a key to pause it, or delete it to revoke it for good; request history is kept either way. Key changes take effect within a few seconds. Up to 25 keys can be active per account.
Keep keys on the server
SDKs & frameworks
| Client | Configuration |
|---|---|
| OpenAI Python | OpenAI(base_url=…, api_key=…) |
| OpenAI Node / TypeScript | new OpenAI({ baseURL, apiKey }) |
| Vercel AI SDK | createOpenAICompatible({ name: "ditfa", baseURL, apiKey }) |
| LangChain | ChatOpenAI(base_url=…, api_key=…, model=…) |
| Tools that read env vars | OPENAI_BASE_URL=https://gateway.ditfa.com/v1 OPENAI_API_KEY=sk-dp-… |
API reference
Create a chat completion
Request body
modelstringrequired- Model slug in
author/nameform, e.g.openai/gpt-4o-mini. See List models. messagesarrayrequired- At least one message. Roles:
system,developer,user,assistant,tool.contentmay be a string or an array of content parts (text and, for vision models, images). streamboolean- Default
false. Whentrue, the response is a stream of server-sent events. See Streaming. max_completion_tokensinteger- Output cap.
max_tokensis accepted too (if both are sent,max_completion_tokenswins). Without either, the gateway applies a cap of 16,384 tokens or the model's maximum output, whichever is lower. temperaturenumber- Sampling temperature, usually 0 to 2.
top_pnumber- Nucleus sampling probability mass.
stopstring | string[]- Sequences where generation stops.
presence_penalty / frequency_penaltynumber- Penalties for repeated topics and tokens, where the model supports them.
seedinteger- Best-effort deterministic sampling, where supported.
tools / tool_choice / parallel_tool_callsarray / string | object / boolean- Function calling in the OpenAI format. Only for models whose
supported_parametersincludetools. response_formatobject{ "type": "json_object" }or ajson_schemafor structured output, where supported.logprobs / top_logprobsboolean / integer- Token log probabilities, where supported.
Unsupported fields are dropped
user, n or vendor extensions) are ignored without an error. Support for optional parameters varies by model; check supported_parameters.Response
A chat.completion object. id is the Ditfa generation id and model is the public slug you requested, whichever provider served it.
{
"id": "gen-3f1c2a7e-8b4d-4c1e-9a0f-5d6e7b8c9d0e",
"object": "chat.completion",
"created": 1793318400,
"model": "openai/gpt-4o-mini",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Waves fold into foam…"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 14,
"completion_tokens": 17,
"total_tokens": 31
}
}Response headers
x-ditfa-generation-idstringgen-…id of a successful request. Use it with /generation to read its final token counts and cost.x-ratelimit-limit-requestsinteger- Requests allowed per minute for this key.
x-ratelimit-remaining-requestsinteger- Requests left in the current one-minute window.
retry-afterinteger- Seconds to wait. Sent with 429 rate_limit_exceeded.
Streaming
stream: true to receive server-sent events (text/event-stream) in the OpenAI chunk format, ending with data: [DONE].curl https://gateway.ditfa.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DITFA_API_KEY" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{
"role": "user",
"content": "Write a haiku about the ocean."
}
]
}'data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant","content":"Waves"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{"content":" fold"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":17,"total_tokens":31}}
data: [DONE]- The last chunk before
[DONE]carriesusagewith an emptychoicesarray. The gateway always requests it, so you do not needstream_options. Guard against emptychoiceswhen reading deltas. - Chunk
ids come from the provider. Use thex-ditfa-generation-idresponse header to look the request up later. - If you disconnect mid-stream, you pay only for the output already delivered. A stream that delivered nothing is not charged.
List models
curl https://gateway.ditfa.com/v1/models{
"data": [
{
"id": "openai/gpt-4o-mini",
"name": "GPT-4o mini",
"author": "openai",
"created": 1721260800,
"description": "…",
"context_length": 128000,
"architecture": {
"input_modalities": [
"text",
"image"
],
"output_modalities": [
"text"
]
},
"pricing": {
"prompt": "0.000000155",
"completion": "0.000000618",
"input_cache_read": "0.0000000773",
"request": "0"
},
"top_provider": {
"context_length": 128000,
"max_completion_tokens": 16384
},
"supported_parameters": [
"max_tokens",
"temperature",
"top_p",
"stop",
"tools",
"tool_choice"
]
}
]
}Fields
idstring- The slug to pass as model.
pricingobject- USD per token as decimal strings (OpenRouter format):
prompt,completion,input_cache_read(absent when the model has no cached-input price) andrequest(fixed fee per request). Multiply by 1,000,000 for the per-million price shown on model pages. context_lengthinteger- Maximum context window in tokens.
top_providerobject- Context window and max output tokens of the provider behind the listed price.
architectureobject- input_modalities and output_modalities, e.g. ["text", "image"].
supported_parametersstring[]- Optional request parameters the model accepts, including tools and reasoning.
Get the current key
curl https://gateway.ditfa.com/v1/key \
-H "Authorization: Bearer $DITFA_API_KEY"{
"data": {
"limit": 50,
"limit_period": "month",
"usage": 12.4,
"limit_remaining": 37.6,
"allowed_models": []
}
}Amounts are USD. limit and limit_remaining are null when the key has no spend limit; usage is then the key's lifetime spend.
Get credits
{
"data": {
"balance": 24.5,
"reserved": 0.02,
"available": 24.48
}
}reserved is held for requests in flight; available = balance − reserved is what new requests can use.
Get a generation
{
"data": {
"id": "gen-3f1c2a7e-8b4d-4c1e-9a0f-5d6e7b8c9d0e",
"model": "openai/gpt-4o-mini",
"created_at": "2026-10-30T09:12:44.512Z",
"status": "ok",
"streamed": true,
"tokens_prompt": 14,
"tokens_completion": 17,
"tokens_cached": 0,
"tokens_reasoning": 0,
"total_cost": 0.000013,
"latency_ms": 812,
"ttft_ms": 241
}
}total_cost is the USD amount charged. status is ok, error or canceled; ttft_ms (time to first token) is set for streams. Usage is written once the request finishes, so query it after the response or stream ends. Any key of the same account can read it.
Guides
Billing & credits
You pay per token in USD from a prepaid wallet. Prompt tokens are billed at the input rate, cached prompt tokens at the cache-read rate when the model has one, and output tokens (including reasoning tokens) at the output rate, plus any fixed per-request fee.
How a request is charged
- Reserve. Before calling the model, the gateway holds an upper bound: the estimated prompt plus the maximum output (
max_tokens, else 16,384, capped by the model). If available credits or the key's limit cannot cover it, you get 402. - Settle. When the response ends, the real usage is charged and the rest of the hold is released.
- Release. A failed request releases the whole hold. You are never charged for 4xx errors or for 502s.
Low balance, long answers?
max_tokens to what you really need. The hold shrinks with it, so requests keep working on a small balance instead of failing with insufficient_credits.Top-ups are $5 to $1,000 each and credits never expire. A low-balance email alert can be set in the settings.
Routing & fallback
A model can be served by several providers. The gateway tries up to three of them in priority order and moves providers that are currently failing for several users to the back of the line.
- If a provider times out, returns 408, 429 or 5xx, or cannot be reached, the next one is tried automatically. Nothing has been sent to you yet, so fallback is invisible.
- A 400 from a provider means the request itself was rejected; it is not retried elsewhere.
- You are billed at the rate of the provider that answered. The catalog shows the lowest available price; /generation shows exactly what you paid.
- The provider policy on a key or the account (verified providers only, no prompt logging) removes providers from routing. If none are left you get
403 no_allowed_provider.
Errors
error.code (stable); message is for humans and may change.{
"error": {
"message": "Insufficient credits. Top up your wallet at https://platform.ditfa.com/credits.",
"type": "payment_required",
"code": "insufficient_credits",
"param": null
}
}| Status | Code | Meaning | Retry? |
|---|---|---|---|
| 400 | nullinvalid_request_error | The body is not valid JSON, model or messages is missing, or the model rejected the parameters. The message names the problem. | No |
| 401 | missing_api_keyauthentication_error | No Authorization: Bearer header was sent. | No |
| 401 | invalid_api_keyauthentication_error | The key is malformed, wrong, disabled or deleted. | No |
| 401 | wrong_key_typeauthentication_error | A Plan API key (dt-…) was sent to Ditfa Platform. Platform keys start with sk-dp-. | No |
| 401 | key_expiredauthentication_error | The key passed its expiry date. | No |
| 402 | insufficient_creditspayment_required | Available credits cannot cover the reserved cost of this request. Add credits or lower max_tokens. | No |
| 402 | key_limit_reachedpayment_required | The key reached its own spend limit for the current period. Raise the limit or wait for the next period. | No |
| 403 | account_disabledpermission_error | The account that owns the key cannot use the API. Contact support. | No |
| 403 | ip_not_allowedpermission_error | The request came from an IP outside the key's allowlist. | No |
| 403 | model_not_allowedpermission_error | The key's allowed-models list does not include this model. | No |
| 403 | no_allowed_providerpermission_error | No provider of this model matches the key or account provider settings (verified providers only / no prompt logging). | No |
| 404 | model_not_foundinvalid_request_error | The model slug does not exist or is not published. | No |
| 404 | not_foundinvalid_request_error | /generation: no request with this id belongs to your account. | No |
| 429 | rate_limit_exceededrate_limit_error | Too many requests per minute for this key. Wait for the retry-after header. | Yes |
| 429 | model_overloadedrate_limit_error | Every provider of this model is busy right now. | Yes |
| 502 | upstream_unavailableserver_error | No provider could serve the request. You are not charged. | Yes |
| 502 | upstream_errorserver_error | The provider returned an unreadable response. You are not charged. | Yes |
Handling errors
Retry only 429 and 5xx, with exponential backoff and jitter, and honour retry-after. Treat 401, 402, 403 and 404 as configuration problems: retrying will not help.
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://gateway.ditfa.com/v1',
apiKey: process.env.DITFA_API_KEY,
maxRetries: 3, // retries 429 and 5xx with backoff, honouring retry-after
});
try {
const completion = await client.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(completion.choices[0].message.content);
} catch (error) {
if (error instanceof OpenAI.APIError) {
switch (error.code) {
case 'insufficient_credits':
case 'key_limit_reached':
// Not retryable: alert someone to top up or raise the key limit.
break;
case 'model_not_found':
case 'model_not_allowed':
// Fix the model slug or the key's allowed models.
break;
default:
console.error(error.status, error.code, error.message);
}
}
throw error;
}Used the wrong key?
dt- key sent to Ditfa Platform (or an sk-dp- key sent to the Plan API) gets 401 wrong_key_type with a message that says which product it belongs to.Rate limits & quotas
| Limit | Value |
|---|---|
| Requests per key | 600 per minute by default. Read x-ratelimit-* headers on every response. |
| Requests per IP | 1,000 per minute across all keys from one address. |
| Active keys | 25 per account. |
| Top-up size | $5 to $1,000 per payment; credits do not expire. |
| Default output cap | 16,384 tokens, or the model's maximum when lower. Set max_tokens to change it. |
| Context & max output | Per model; see /models or each model page. |
Need a higher request rate? Contact us with your expected volume.
App attribution
HTTP-Referer: https://your-app.example
X-Title: Your AppReferer is accepted when HTTP-Referer is absent. Only the origin of the referer is ever shown publicly, never its path or query. Both values also appear in your own request log.
Best practices
- Use one key per app or environment, each with its own spend limit, so a leak or a bug has a ceiling.
- Add an IP allowlist for keys used from fixed servers, and an expiry for temporary keys.
- Restrict production keys to the models you actually use, so a typo cannot route to a much pricier model.
- Log
x-ditfa-generation-idwith your own request ids; it makes cost questions and support tickets quick to answer. - Check
GET /creditsfrom a cron job or set a low-balance alert so production never hitsinsufficient_credits.
Plan API vs Ditfa Platform: which one do I need?
| Plan API Included in your plan | Ditfa Platform $ Pay-as-you-go | |
|---|---|---|
| Billing | Included in your Ditfa plan (usage quota) | Pay-as-you-go from prepaid USD credits |
| Base URL | https://api.ditfa.com/api/v1 | https://gateway.ditfa.com/v1 |
| Key prefix | dt-… | sk-dp-… |
| Where to manage | Chat → Integrations → API | platform.ditfa.com |
| Models | Models your plan enables for the API | The full public catalog with per-token prices |
| When it runs out | Wait for the next usage window or upgrade your plan | Add credits; no windows |
| Best for | Testing and personal scripts | Products, higher volume and specific models |
Open the Plan API
Testing and personal use, within your plan's usage.
Open Ditfa Platform
Products, volume and any model, paid per token.
Host your own model? Sell it to every Ditfa Platform developer at your own price through the Ditfa Provider Program.Become a provider