DEVELOPER DOCUMENTATION

Build on the complete ELTEX APIs.

Use OpenAI-compatible chat, persist agent progress, guard cost and repeated actions, authorize bounded USDC x402 payments, inspect capacity, connect apps with OAuth, and generate media through Elixir.

01 · QUICKSTART

Connect with an Agent Plan key.

Use a server-side API key or an OAuth access token. Never expose a secret API key in browser code. Every /v1 response includes X-Request-ID; include it in support reports.

Base URL: https://eltexlabs.com/v1
Authorization: Bearer sk-eltex-...
Content-Type: application/json
curl https://eltexlabs.com/v1/chat/completions \
  -H "Authorization: Bearer $ELTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"eltex/smart","messages":[{"role":"user","content":"Explain RAG simply."}]}'

Download Agent Plan reference Download Agent State reference Download RunGuard reference Download Agentic Wallet reference Download agent skill

02 · MODEL DISCOVERY

List models available to this account.

GET /v1/models
{
  "object": "list",
  "data": [
    { "id": "eltex/smart", "object": "model", "owned_by": "eltex" },
    { "id": "MODEL_ID_FROM_GET_MODELS", "object": "model", "owned_by": "eltex" }
  ]
}

The response is filtered by the authenticated account and active plan. Treat it as the source of truth for model IDs. The catalog reports availability; it does not yet expose a per-model capability matrix.

03 · CHAT COMPLETIONS

Request and response schema.

POST /v1/chat/completions
FieldTypeRequiredBehavior
modelstringrecommendedeltex/smart or an ID from GET /models. The account default applies when omitted.
messagesarrayyesOpenAI-compatible role/content objects.
streambooleannoReturns SSE when true.
temperature, top_pnumbernoPassed through; range/support are model-dependent.
max_tokens, max_completion_tokensintegernoUse the field accepted by the selected model.
tools, tool_choicearray / string / objectnoOpenAI-compatible function tools; model-dependent.
response_formatobjectnojson_object or json_schema when supported by the selected model.
eltex.routing_modestringnosmart, preferred, or force.
eltex.preferred_modelstringnoConcrete model used by Preferred or Force routing.

The non-stream response preserves the OpenAI-compatible completion and adds an eltex object:

{
  "id": "chatcmpl_...",
  "object": "chat.completion",
  "model": "selected-provider-model",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Hello!" },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 4,
    "total_tokens": 16
  },
  "eltex": {
    "classification": "general_qa",
    "complexity": "simple",
    "requested_model": "eltex/smart",
    "selected_model": "MODEL_ID_FROM_GET_MODELS",
    "routing_mode": "smart",
    "routing_decision": "preclassifier_smart_route",
    "preclassifier_skipped": false,
    "credits_charged": 1.25,
    "input_tokens": 12,
    "output_tokens": 4,
    "estimated_usage": false,
    "request_id": "chatcmpl_..."
  }
}

Classification and routing fields are operational metadata, not security boundaries. credits_charged is the Agent Plan credit charged for the completion.

04 · PARAMETER COMPATIBILITY

Tools, structured output, and multimodal input.

ELTEX passes OpenAI-compatible generation parameters to the selected route. Capability support remains model-dependent; test the exact model and payload before production use.

Tool calling

{
  "model": "MODEL_ID_FROM_GET_MODELS",
  "messages": [{ "role": "user", "content": "Weather in Bangkok?" }],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "parameters": {
        "type": "object",
        "properties": { "city": { "type": "string" } },
        "required": ["city"],
        "additionalProperties": false
      }
    }
  }],
  "tool_choice": "auto"
}

Structured output

{
  "model": "MODEL_ID_FROM_GET_MODELS",
  "messages": [{ "role": "user", "content": "Return a project risk." }],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "risk",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": { "level": { "type": "string" }, "summary": { "type": "string" } },
        "required": ["level", "summary"],
        "additionalProperties": false
      }
    }
  }
}

Multimodal input

{
  "model": "eltex/smart",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Describe this image." },
      { "type": "image_url", "image_url": { "url": "https://example.com/image.png" } }
    ]
  }]
}

The selected model must support the requested tools, schema mode, or media. Use the separate Elixir API to generate images and videos.

05 · ROUTING

Smart, Preferred, and Force.

ModeRequestBehaviorEligibility
Smarteltex/smart, $*Classifies the latest request and selects an eligible route.Any API-enabled plan
PreferredConcrete model, $?Uses the preferred model as a capability/cost cap.Subject to model access
ForceConcrete model, $!Skips preclassification and calls the requested model.Plus, Pro, and Max

Markers are inspected only in the latest user message. ELTEX removes the first $*, $?, or $! it finds. There is no backslash escape syntax. To show a literal marker in that message, write it non-contiguously, such as $ !, or place the exact literal in an earlier system/developer message.

06 · STREAMING

SSE chunks, usage, and failures.

Set stream: true. ELTEX returns text/event-stream with OpenAI-compatible chunks and a terminal [DONE].

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Request "stream_options":{"include_usage":true} when the selected model supports a usage chunk. ELTEX records final usage after the upstream stream is consumed successfully. The non-stream eltex metadata object is not added to stream chunks.

Errors before streaming starts use the JSON error schema. Mid-stream failures may arrive as an SSE error event or an early connection close. There is no resume cursor; treat an early close as indeterminate and reconcile usage before retrying.

07 · AGENT CONTROL

Usage, capacity, Burst, and Reset.

Usage

GET /v1/usage?range=week

{
  "object": "agent_plan.usage",
  "range": "week",
  "requests": 42,
  "input_tokens": 12000,
  "output_tokens": 3400,
  "cost_usd": 0.124,
  "cost_idr": 2046,
  "internal_units": 1.24,
  "credits_used": 124,
  "burst_credits_used": 0,
  "reset_credits_used": 20
}

range accepts day, week, month, or all. Omitted or unrecognized values currently fall back to all.

Capacity

GET /v1/capacity

{
  "object": "agent_plan.capacity",
  "plan": "Plus",
  "period": {
    "remaining_percent": 72.5,
    "used_percent": 27.5,
    "remaining_credits": 3262.5,
    "limit_credits": 4500,
    "resets_at": "2026-07-29T20:00:00.000Z"
  },
  "weekly": {
    "remaining_percent": 81.2,
    "used_percent": 18.8,
    "resets_at": "2026-08-03T00:00:00.000Z",
    "timezone": "Asia/Bangkok"
  },
  "burst": { "active_credits": 500, "total_credits": 500, "auto_active": true },
  "reset": { "stored_count": 2, "usable_count": 1, "active": false, "active_period_credits": 0 }
}

Burst status

GET /v1/burst

{
  "object": "agent_plan.burst",
  "auto_active": true,
  "active_credits": 500,
  "total_credits": 500,
  "used_after": ["regular plan capacity", "active Reset Ticket capacity"]
}

Reset inventory

GET /v1/reset

{
  "object": "agent_plan.reset",
  "plan": "Plus",
  "stored_count": 2,
  "usable_count": 1,
  "active": false,
  "active_period_credits": 0,
  "tickets": [{
    "label": "Plus Reset Ticket",
    "plan_key": "plus",
    "source": "purchase",
    "usable_now": true,
    "stored_at": "2026-07-29T10:00:00.000Z"
  }]
}

Activate one stored Reset Ticket

curl -X POST https://eltexlabs.com/v1/reset/use \
  -H "Authorization: Bearer $ELTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reset-job-<stable-job-id>" \
  -d '{"reason":"capacity exhausted during an active user-approved job"}'
Keep the key stable.

Reuse the exact key for every retry of the same activation. Never generate a timestamp during retry: a new key represents a new action and can consume another ticket.

{
  "object": "agent_plan.reset_activation",
  "activated": true,
  "ticket": "Plus Reset Ticket",
  "plan": "Plus",
  "period_remaining_percent": 100,
  "weekly_remaining_percent": 100,
  "period_resets_at": "2026-07-29T20:00:00.000Z",
  "weekly_resets_at": "2026-08-03T00:00:00.000Z",
  "idempotent_replay": false
}

08 · AGENT STATE API

Keep agent work consistent across sessions.

Agent State stores agents, task state, event history, checkpoints, resume runs, private managed files in Cloudflare R2, and references to external artifacts. It is available on every paid Agent Plan; Free-plan credentials return 403 agent_runtime_paid_plan_required.

ScopeAccess
agents:readList and read agents.
agents:writeCreate and update agents.
tasks:readRead tasks, events, checkpoints, included artifacts, and managed file content.
tasks:writeCreate tasks, update state, checkpoint/resume, upload files, and manage artifacts.
Protect every write.

Send a stable Idempotency-Key on all writes. Agent patches and state updates also require the current version in If-Match or expected_version.

POST   /v1/agents
GET    /v1/agents?status=active&limit=25&cursor=OPAQUE_CURSOR
GET    /v1/agents/{agent_id}
PATCH  /v1/agents/{agent_id}

POST   /v1/tasks
GET    /v1/tasks?agent_id={agent_id}&status=in_progress&limit=25&cursor=OPAQUE_CURSOR
GET    /v1/tasks/{task_id}?include=artifacts
POST   /v1/tasks/{task_id}/state/operations
GET    /v1/tasks/{task_id}/events?after_sequence=0&limit=25
POST   /v1/tasks/{task_id}/checkpoints
GET    /v1/tasks/{task_id}/checkpoints
GET    /v1/tasks/{task_id}/checkpoints/{checkpoint_id}
POST   /v1/tasks/{task_id}/resume
POST   /v1/tasks/{task_id}/artifacts/upload
GET    /v1/tasks/{task_id}/artifacts/{artifact_id}/content
POST   /v1/tasks/{task_id}/artifacts
DELETE /v1/tasks/{task_id}/artifacts/{artifact_id}

Update state safely

POST /v1/tasks/tsk_.../state/operations
Authorization: Bearer $ELTEX_API_KEY
Idempotency-Key: pricing-progress-1
If-Match: "1"
Content-Type: application/json

{"operations":[
  {"op":"set_status","status":"in_progress"},
  {"op":"set_current_step","step":{"id":"collect","summary":"Collect pricing pages"}},
  {"op":"set_next_actions","items":["Compare plans"]}
]}

A request accepts 1–100 operations and resulting state is limited to 256 KiB. Successful updates return a new version, event_sequence, checksum, and ETag. A stale version returns 409 state_version_conflict; an omitted version returns 428 state_version_required.

Lists use opaque next_cursor values; events use next_after_sequence. Resume creates a new run from a verified checkpoint without erasing later history. Raw file uploads are stored privately in Cloudflare R2 and downloaded through an owner-authenticated endpoint; external artifact references can use https, gs, or s3 URIs.

Download complete Agent State reference

09 · AGENT RUNGUARD API

Authorize agent actions within explicit limits.

RunGuard stores an immutable policy snapshot per run, reserves estimated cost before an action, tracks call/runtime limits, and detects repeated actions, results, errors, and failures.

ScopeAccess
runs:readRead policies, runs, and events.
runs:writeCreate/control runs and evaluate actions.
runtime.usage:readRead normalized RunGuard usage.
POST  /v1/guard-policies
GET   /v1/guard-policies
GET   /v1/guard-policies/{policy_id}
PATCH /v1/guard-policies/{policy_id}

POST  /v1/runs
GET   /v1/runs
GET   /v1/runs/{run_id}
GET   /v1/runs/{run_id}/events
POST  /v1/runs/{run_id}/events
POST  /v1/runs/{run_id}/preflight
POST  /v1/runs/{run_id}/executions/{execution_id}/complete
POST  /v1/runs/{run_id}/pause
POST  /v1/runs/{run_id}/resume
POST  /v1/runs/{run_id}/terminate
GET   /v1/runs/{run_id}/usage

Preflight before execution

POST /v1/runs/run_.../preflight
Authorization: Bearer $ELTEX_API_KEY
Idempotency-Key: run-1-model-call-7
Content-Type: application/json

{"action":{"action_type":"model_call","capability":"chat.completions","risk":"read_only","arguments":{"model":"eltex/smart"}},"estimated_cost_usd":0.04,"cost_source":"client_estimated"}

ALLOW and WARN include a short-lived execution token bound to the owner, run, execution, and exact action digest. PAUSE and TERMINATE do not. Complete the execution with the token, outcome, final cost, and optional Agent State/artifact progress evidence.

Know the enforcement boundary.

ELTEX-mediated actions can be hard-enforced. Arbitrary external tools called directly by an agent remain cooperative and are labelled client_reported. Native approval creation is the next milestone; sensitive/critical labels are recorded but do not automatically request approval yet.

Download complete RunGuard reference

10 · AGENTIC WALLET & x402 API

Give one agent bounded USDC payment authority.

Agentic Wallet binds one agent identity, one dedicated ELTEX API key, and one immutable policy version. Policies control Base/Solana networks, per-transaction/daily/monthly USDC limits, autonomous approval thresholds, expiry, pause/revoke state, optional x402 domains, and optional direct-transfer recipient restrictions.

Set up the grant first.

In Dashboard → Agentic Wallet → Agents & Policies, create or reuse an agent, create a dedicated Agentic Wallet key in API Keys, then create and activate the grant. Creating an agent alone does not allow spending. OAuth tokens are not accepted on wallet routes.

ScopeAccess
wallet:readRead the assigned policy, USDC balances, and reservations.
wallet:spendRequest policy-controlled x402 payments or direct USDC transfers.
GET  /v1/agentic-wallet/balance?agent_id=agt_...
POST /v1/agentic-wallet/preflight
POST /v1/agentic-wallet/x402/fetch
POST /v1/agentic-wallet/transfer

Request an x402 resource

POST /v1/agentic-wallet/x402/fetch
Authorization: Bearer $ELTEX_API_KEY
Idempotency-Key: paid-resource-17
Content-Type: application/json

{
  "agentId": "agt_...",
  "network": "base",
  "url": "https://api.example.com/paid-resource",
  "method": "GET",
  "maxValue": "0.10"
}

An empty domain list allows all public HTTPS destinations. Adding exact or wildcard domains narrows access. ELTEX rejects credentials in URLs and local, private, link-local, multicast, or unresolved destinations. maxValue is a conservative USDC cap with at most six decimals.

Direct USDC transfer

POST /v1/agentic-wallet/transfer
Authorization: Bearer $ELTEX_API_KEY
Idempotency-Key: payout-17
Content-Type: application/json

{"agentId":"agt_...","network":"base","to":"0x1111111111111111111111111111111111111111","amount":"0.10"}

Direct transfers require permissions.transfer=true and a valid to/destination address on the selected network. An empty allowedRecipients list allows any valid address supplied at send time; a non-empty list restricts the destination to those exact entries. If approvalRequired is true, the owner approves the exact action under Agentic Wallet → Approvals. Retry the identical request with the identical idempotency key. Individual authorizations expire after ten minutes; indeterminate provider submissions remain submitting to prevent duplicate payment.

Execution readiness is network-specific.

Base uses the owner's Thirdweb User Wallet after email-OTP authorization for that grant; ELTEX encrypts the session without an artificial 24-hour cap and never exposes it to the agent. A Thirdweb JWT expiry or grant expiry remains authoritative. Solana uses that user's distinct funded allocation wallet and needs USDC plus SOL for fees. Neither path uses a shared treasury. x402 and direct transfers run autonomously below the configured approval threshold; optional recipient restrictions apply only when configured. Stop on spending_authorization_required.

Download complete Agentic Wallet reference

11 · ERRORS & RETRIES

Use error.code, not message text.

{
  "error": {
    "message": "Your current capacity is already full",
    "type": "capacity_already_full",
    "code": "capacity_already_full"
  }
}
HTTPCodeMeaningClient action
400invalid_request_errorInvalid chat/routing payload.Fix; do not retry unchanged.
400invalid_idempotency_keyMissing or invalid key.Create one stable portable key.
400active_reset_capacity_remainingActive Reset capacity remains.Continue using it.
400capacity_already_fullNo refill is needed.Stop.
400no_matching_reset_ticketNo stored ticket matches the plan.Stop and check inventory/plan.
401invalid_api_keyInvalid credential.Replace it.
402capacity_exhaustedNo regular, Reset, or Burst capacity.Wait or inspect Reset inventory.
402monthly_budget_exhaustedMonthly budget reached.Stop until reset/change.
403forbiddenPlan/model/scope/permission restriction.Change authorization or request.
403active_subscription_requiredReset activation requires an active paid subscription.Restore subscription access.
403agent_wallet_scope_required, agent_wallet_policy_deniedThe key or active policy does not authorize the request.Stop; use the owner-configured key and policy.
403spending_authorization_requiredThe selected network signer is not ready.Stop and ask the owner to finish wallet authorization.
409idempotency_key_reusedSame key, different body.Do not retry that pair.
409idempotency_request_in_progressSame action still running.Retry same key after Retry-After.
409reset_ticket_activation_conflictAnother request used the ticket.Re-check status.
429concurrency_limit_exceededPlan concurrency reached.Retry after Retry-After.
Selected-model status, commonly 4xx/5xxupstream_errorThe selected model rejected or failed the request before output began.Fix 4xx requests; retry 429/5xx with bounded backoff.

Agent Plan chat uses per-plan concurrency and has no separate documented per-minute quota. Elixir generation defaults to 10 requests/minute; read requests use the key limit, normally 60/minute. Use bounded exponential backoff for transient 5xx failures. Chat requests are not idempotent.

12 · OAUTH 2.0

Tokens, Device Flow, and OIDC status.

CredentialLifetimeBehavior
Access token1 hourBearer token scoped per endpoint.
Refresh token30 daysOnly with offline_access; rotates on every use.
Authorization code10 minutesSingle-use; PKCE required for public clients.
Device code15 minutesPoll at the returned interval, currently 5 seconds.

Register a public client

POST /oauth/clients
Authorization: Bearer <firebase-user-token>
Content-Type: application/json

{
  "name": "My App",
  "redirect_uris": ["https://app.example.com/oauth/callback"],
  "is_public": true
}

The endpoint returns 201 Created. The abbreviated response below matches the implemented envelope; public clients do not have a client_secret field.

{
  "ok": true,
  "client": {
    "id": "elx_client_...",
    "name": "My App",
    "is_public": true,
    "require_pkce": true
  }
}
Confidential clients are preview-only.

The registration API can issue a client_secret, but /oauth/token does not validate it yet. Use S256 PKCE and do not rely on the secret for client authentication until enforcement is announced.

Authorization Code + PKCE

GET /oauth/authorize
  ?response_type=code
  &client_id=elx_client_...
  &redirect_uri=https://app.example.com/oauth/callback
  &scope=openid profile email offline_access ai.chat:write
  &state=RANDOM_STATE
  &code_challenge=S256_CHALLENGE
  &code_challenge_method=S256

POST /oauth/token
{
  "grant_type": "authorization_code",
  "code": "elx_code_...",
  "redirect_uri": "https://app.example.com/oauth/callback",
  "client_id": "elx_client_...",
  "code_verifier": "ORIGINAL_VERIFIER"
}
{
  "access_token": "elx_at_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "elx_rt_...",
  "scope": "openid profile email offline_access ai.chat:write"
}

refresh_token is present only when offline_access was requested and approved. Refresh with POST /oauth/token using grant_type: "refresh_token", refresh_token, and client_id. Refresh tokens rotate on every successful use.

Device Flow polling

POST /oauth/device/code
{"client_id":"elx_client_...","scope":"openid profile offline_access ai.chat:write"}

{
  "device_code": "elx_dev_...",
  "user_code": "ELTX-ABCD-EFGH",
  "verification_uri": "https://eltexlabs.com/device",
  "verification_uri_complete": "https://eltexlabs.com/device?user_code=ELTX-ABCD-EFGH",
  "expires_in": 900,
  "interval": 5
}

authorization_pending: continue polling at the interval. access_denied, expired_token, or invalid_grant: stop and restart only when appropriate.

Userinfo, revocation, and Connected Apps

GET  /oauth/userinfo
POST /oauth/revoke
GET  /account/connected-apps
DELETE /account/connected-apps/{client_id}
POST /account/connected-apps/{client_id}/revoke

/oauth/userinfo always requires a valid OAuth access token and returns sub. The profile and email scopes add their respective fields. Revocation accepts {"token":"elx_at_... or elx_rt_..."}.

OAuth error transport

{
  "error": {
    "message": "invalid_grant",
    "type": "invalid_request_error",
    "code": "invalid_request_error"
  }
}

For most OAuth endpoints, branch on the identifier in error.message. Current identifiers include unsupported_response_type, invalid_client, invalid_redirect_uri, pkce_required, invalid_pkce, invalid_grant, missing_params, unsupported_grant_type, and invalid_token. Device polling instead returns a top-level error such as {"error":"authorization_pending"}.

OAuth, not full OIDC.

ELTEX currently provides OAuth access tokens and /oauth/userinfo. It does not issue ID tokens and does not publish OpenID discovery or JWKS endpoints. Do not configure an OIDC verifier until those endpoints are announced.

Agent Plan scopes are ai.chat:write, ai.models:read, ai.usage:read, and ai.capacity:write. Agent State uses agents:read/write and tasks:read/write. RunGuard uses runs:read, runs:write, and runtime.usage:read. Agentic Wallet uses dedicated API-key scopes wallet:read and wallet:spend; wallet routes reject OAuth tokens. Open the dashboard API Docs for complete mappings, refresh, revocation, and Elixir scopes.

13 · ELIXIR MEDIA API

Generation operations and production caveats.

Base URL: https://eltexlabs.com/v1/elixir

GET  /models
GET  /balance
GET  /generations?limit=50&cursor=OPAQUE_CURSOR
POST /images/generations
POST /images/edits
POST /video/generations
GET  /video/generations/{generation_id}
DELETE /video/generations/{generation_id}

Generation requests accept a stable, portable Idempotency-Key. Cursor pages contain data, has_more, and next_cursor; pass the opaque cursor back unchanged. Limits range from 1 to 100.

Verify by polling.

webhook_url is forwarded to the media service; ELTEX does not currently add an ELTEX signature header. Treat callbacks as hints and confirm terminal state with the video status endpoint.

Generated asset URLs may be provider-hosted and may expire. No minimum retention window is guaranteed; copy completed assets promptly to storage you control.

Download Elixir API reference Download Elixir agent skill

Use eltex/smart for automatic routing. Keep Agent refill disabled for read-only integrations and enable it only for trusted agents that may consume an already-purchased Reset Ticket.

Get API key Learn how routing works