External Models API
The External Models API runs a chat model of an external provider with that model's native input and returns its native output unchanged. Every model has a versioned contract with its input and output JSON Schemas and its per-token price. The maximum charge is reserved on your FlyMyAI balance when you start an operation, and the exact charge for the reported usage is taken once when it finishes.
Route keys name the provider and the model, for example openrouter/meta-llama/llama-3.1-8b-instruct.
Authentication and base URL
- Base URL:
https://backend.flymy.ai/api/v1/model-proxy/ - Header:
X-API-KEY: <your FlyMyAI API key>(see Authentication). Web sessions can use their bearer token instead. - A request without credentials gets
403.
1. Find a model
GET /catalog/?prefix=<route prefix>&limit=<1..100>&cursor=<next_cursor> lists the models you can run:
{"release": "d2a3f6eb-…", "items": [{
"profile": "openrouter.chat.v1", "operation": "chat.completions",
"route_key": "openrouter/meta-llama/llama-3.1-8b-instruct",
"model_version": "meta-llama/llama-3.1-8b-instruct",
"contract_revision": "91e3f184ebc3003f6fdec0544c1de1b91add6a118d1fd44df00e69473355784d"}],
"next_cursor": null}
A cursor is valid for one hour, for the same user and prefix.
2. Read the contract
GET /contracts/<contract_revision>/ returns the exact contract:
- the native input and output JSON Schemas (
artifact.schemas); - the price (
artifact.price); - the limits: input and output bytes, and a deadline;
execution_constraints.
The revision is a SHA-256 of the contract, so a newer catalog never changes an operation you already started.
3. Start an operation
POST /operations/ with an Idempotency-Key header (your unique key for this exact request) and the native input:
POST /api/v1/model-proxy/operations/
X-API-KEY: <key>
Idempotency-Key: order-42-summary
Content-Type: application/json
{"contract_revision": "91e3f184…",
"inputs": {"model": "meta-llama/llama-3.1-8b-instruct",
"messages": [{"role": "user", "content": "Reply with exactly OK."}],
"max_tokens": 8, "temperature": 0}}
The answer is 202 Accepted with "created": true:
{"operation": {"operation_id": "6fdc75e7-…", "prediction_id": "6fdc75e7-…",
"status": "queued", "stage": "submit",
"contract_revision": "91e3f184…", "cancel_requested": false},
"created": true}
Repeating the same key with the same body returns the same operation (200, "created": false) and never starts a second paid call. The same key with a different body gets 409 idempotency_key_reused.
4. Wait for the result
Poll GET /operations/<operation_id>/ until status is final. When it is succeeded, output holds the provider's native result:
{"operation": {"operation_id": "6fdc75e7-…", "status": "succeeded", "stage": "submit",
"output": {"id": "gen-…", "object": "chat.completion",
"choices": [{"index": 0, "message": {"role": "assistant", "content": "OK."},
"finish_reason": "stop"}],
"usage": {"prompt_tokens": 32, "completion_tokens": 3, "total_tokens": 35}}}}
| Status | Meaning |
|---|---|
queued | Admitted and waiting for a provider slot |
running | Sent to the provider |
succeeded | Final; output is set and the exact charge is taken |
failed | Final; no charge (for example, the request never reached the provider) |
cancelled | Final; cancelled before it was sent, no charge |
rejected | Final; refused before it was sent, no charge |
unknown | The provider may have done the work but did not answer clearly. The maximum charge stays reserved until FlyMyAI reconciles it with the provider; the request is never sent twice |
5. Cancel
POST /operations/<operation_id>/cancel/ with the body {}.
- Before the request was sent: the operation ends
cancelledand the reservation is released. - After it was sent: the provider offers no proven way to cancel or refund, so
cancel_requestedbecomestrue, and the result is still delivered and charged.
Prices and charges
- At the start the maximum charge is reserved on your balance: the contract's per-million token rates (
kind: "tokens") times itsmax_usage, the most one call can use. - On success the exact charge is taken once: the provider's reported usage at the contract's per-million rates. The rest of the reservation is released.
- Failed, cancelled and rejected operations release the reservation without a charge.
- An
unknownoutcome keeps the reservation until reconciliation.
Errors
| HTTP | code | When |
|---|---|---|
| 400 | proxy_input | The input does not match the contract's native schema (nothing reserved, nothing sent) |
| 400 | invalid_idempotency_key, proxy_contract, proxy_json, proxy_profile_constraint, proxy_prompt_bound | Missing or invalid key, missing exact revision, text that cannot be stored, a profile rule (for example stream: true) or a prompt above the contract's priced bound |
| 402 | proxy_balance | The balance cannot cover the maximum charge |
| 403 | - / proxy_permission, proxy_route_stale | No credentials; no access to the model; an inactive account or a contract past its validity |
| 404 | proxy_contract_missing, proxy_operation_missing | Unknown or invisible contract; an operation of another payer |
| 409 | idempotency_key_reused | Same key, different request |
| 409 | proxy_route_revoked, proxy_route_disabled | This contract revision is no longer enabled: read the catalog again and use the current revision. Nothing is reserved |
| 413 | proxy_bytes, proxy_depth | Request body above 264 KiB or the contract's input limit; JSON nested too deep |
| 429 | proxy_pending_limit | Too many unresolved operations for this payer or provider account; retry later |
| 503 | proxy_receipt_guard and other operational codes | Temporary refusal; nothing reserved; retry later |
Errors have the shape {"code": "...", "detail": "..."}.
What you can run
- Chat (
openrouter.chat.v1): the native chat completions input (messages, tools, sampling fields and more) for models with an exact per-token price. No streaming. Each call goes to the model's cheapest available OpenRouter provider that supports every parameter you send, so a provider never silently ignores one, such asmax_tokens. You cannot choose the provider. - The catalog lists only models whose contract is complete and verified. Streaming, embeddings, image, video and audio models, realtime models and file uploads are not available through this API.
Limits
- Request body: 264 KiB. Per contract:
limits.max_input_bytes,limits.max_output_bytesandlimits.deadline_seconds. An operation not finished by its deadline is not retried with the provider. streammust be absent orfalse.- Provider capacity is shared fairly between payers: queued operations of different payers take turns.