Skip to content

API Reference

Open4X API base:

text
https://api.open4x.com

Application services are mounted under:

text
/v1/apps

Authentication

API Key

Use API keys for server-side integrations:

http
X-API-Key: sk_xxx

API keys are shown once and stored as hashes.

JWT

Use JWT for console-authenticated workflows:

http
Authorization: Bearer <token>

Do not hardcode JWTs in long-running production services.

Registration and login

http
POST /auth/register
POST /auth/login
POST /auth/refresh
POST /auth/logout

Login returns token and refresh_token. Refresh tokens rotate and the previous token becomes invalid after use.

ScopeCapability
bot:sendSend bot messages and replies.
bot:session:createCreate a short-lived Widget Session.
bot:events:readRead incremental Widget conversation events.
file:readRead or download files.
push:sendSend Webhook Push messages.
tron:leaseQuote, create, and query TRON energy lease orders.
jobs:readRead tenant-owned asynchronous job status.
ai:invokeCall AI chat, streaming, and embeddings endpoints.
socialops:writeIngest SocialOps webhook events.
socialops:readRead SocialOps inbox threads and messages.
*Allow all API key scopes in controlled administrative integrations.

An API key with no scopes is denied by scope-protected routes. Do not rely on the old behavior where an empty scope list acted as unrestricted access.

API key policy

Keys can be restricted by service alias, source IP/CIDR, daily UTC quota, and expiry:

json
{
  "name": "production-ai",
  "scopes": ["ai:invoke"],
  "service_aliases": ["ai-model:qwen122"],
  "allowed_ips": ["203.0.113.0/24"],
  "daily_quota": 1000,
  "rate_limit_per_minute": 60,
  "concurrency_limit": 4,
  "expires_at": "2026-12-31T00:00:00Z"
}

Source checks use Cloudflare cf-connecting-ip; forwarded headers supplied by the client are not trusted. Daily quota updates are atomic. An expired key is disabled and recorded in the lifecycle audit log.

Standard Errors

Some current endpoints return a simple error object:

json
{
  "error": "Insufficient balance"
}

Current endpoints still use mostly flat errors with service-specific fields:

json
{
  "error": "Insufficient balance",
  "pricing": {"cost": 0.01}
}

Structured error.code responses are a future normalization direction; do not assume every endpoint returns that field today.

Common status codes:

HTTPCodeMeaning
400invalid_requestThe request body or parameters are invalid.
401unauthorizedMissing or invalid credentials.
402insufficient_balanceThe account does not have enough balance.
403forbiddenThe key or user is missing required permission.
404not_foundThe resource was not found.
409Conflictmax_cost, expired quote, or AI idempotency conflict.
422Business validation failedDefined by the individual service.
429Rate limitedRetry with backoff.
502Upstream failedProvider or bound service failed.

Service Endpoints

ServiceEndpoint
AI chatPOST /v1/apps/ai/:alias/chat
AI streamingPOST /v1/apps/ai/:alias/chat/stream
AI embeddingsPOST /v1/apps/ai/:alias/embeddings
OpenAI-compatible AIGET /v1/apps/ai/:alias/v1/models, POST /v1/apps/ai/:alias/v1/chat/completions, POST /v1/apps/ai/:alias/v1/embeddings
Webhook PushPOST /v1/apps/webhook-push/:alias/send
TRON quotePOST /v1/apps/tron/quote
TRON leasePOST /v1/apps/tron/lease
TRON lease listGET /v1/apps/tron/leases
TRON lease detailGET /v1/apps/tron/leases/:id
Async job listGET /v1/jobs
Async job statusGET /v1/jobs/:id
SocialOps webhookPOST /v1/apps/socialops/:alias/webhook/:provider
SocialOps provider capabilitiesGET /v1/apps/socialops/providers
SocialOps inboxGET /v1/apps/socialops/:alias/inbox
SocialOps threadsGET /v1/apps/socialops/:alias/threads
SocialOps thread messagesGET /v1/apps/socialops/:alias/threads/:thread_id/messages
SocialOps reply queuePOST /v1/apps/socialops/:alias/threads/:thread_id/replies
File uploadPUT /v1/apps/file/upload
File listGET /v1/apps/file/list
File download/shareGET /v1/apps/file/download/*, GET /v1/apps/file/share/*
Bot sendPOST /v1/apps/bot
Bot chats/messagesGET /v1/apps/bot/chats, GET /v1/apps/bot/chats/:chatId/messages
Bot repliesPOST /v1/apps/bot/messages/reply
Bot interactions`GET
Widget SessionPOST /v1/apps/bot/widget/auth, POST /v1/apps/bot/widget/refresh, POST /v1/apps/bot/widget/revoke
Widget aggregate eventsGET /v1/apps/bot/widget/events?cursor=...&limit=100

Creating API keys and services

Console endpoints use a JWT:

http
POST /console/keys
POST /console/keys/{id}/rotate
DELETE /console/keys/{id}
POST /console/services

Create an API key:

bash
curl -X POST https://api.open4x.com/console/keys \
  -H "Authorization: Bearer $OPEN4X_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name":"production-ai","scopes":["ai:invoke"],"service_aliases":["ai-model:qwen122"],"allowed_ips":["203.0.113.0/24"],"daily_quota":1000}'

The plaintext key is returned only once. Rotation returns a replacement key and immediately invalidates the previous secret. Create, rotate, revoke, expiry, policy denial, and quota events are audited without storing the full secret.

Every response includes X-Request-ID. AI usage records use the gateway request ID, not the upstream provider request ID. Tenant administrators can query redacted AI usage and API Key audit metadata with GET /console/requests/:request_id; prompts, answers and secrets are excluded.

Async Jobs and callbacks

TRON lease creation returns a job_id. Query it with an API key carrying jobs:read:

http
GET /v1/jobs/job_xxx

Statuses are pending, running, succeeded, failed, canceled and expired. Lease requests may include an HTTPS callback_url and a 16-256 character callback_secret. job.updated callbacks are signed with X-Open4X-Signature: sha256=<HMAC-SHA256> and retried with exponential backoff up to eight attempts. Sandbox environments do not call external callback URLs.

Create an AI service instance:

bash
curl -X POST https://api.open4x.com/console/services \
  -H "Authorization: Bearer $OPEN4X_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "ai-model",
    "alias": "qwen122",
    "status": "active",
    "config": {
      "provider": "openai-compatible",
      "mode": "byok",
      "base_url": "https://model.example.com/v1",
      "default_model": "my-model",
      "api_key": "provider-key",
      "max_tokens_per_request": 1024,
      "max_cost_per_request": 0.05
    }
  }'

Service instances can also reference an encrypted Connected Account with connected_account_id. Service listings redact secret values.

AI requests and billing headers

bash
curl -X POST https://api.open4x.com/v1/apps/ai/qwen122/chat \
  -H "X-API-Key: sk_xxx" \
  -H "Idempotency-Key: ai-demo-001" \
  -H "Content-Type: application/json" \
  -d '{
    "messages":[{"role":"user","content":"Explain webhooks in three sentences."}],
    "max_tokens":300,
    "max_cost":0.05
  }'

messages must be a non-empty array. If model is omitted, the service default_model is used. Non-streaming responses include:

http
X-OpenEdge-Estimated-Cost: 0.0005
X-OpenEdge-Final-Cost: 0.0005
X-OpenEdge-Total-Tokens: 120

Streaming responses return the estimated cost first and settle after the stream ends; the final cost is not appended to an already established SSE response header. Idempotency-Key accepts 1–128 characters from A-Z a-z 0-9 . _ : -.

Webhook Push

bash
curl -X POST https://api.open4x.com/v1/apps/webhook-push/ops/send \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"title":"Order Paid","text":"Order 10001 has been paid"}'

All targets succeeding returns 200, partial success returns 207, all targets failing returns 502, and insufficient balance returns 402. Targets can be enabled independently. Telegram targets require a Bot Token or Telegram connection and a chat_id.

SocialOps

Ingestion and replies use socialops:write and socialops:read respectively:

bash
curl -X POST https://api.open4x.com/v1/apps/socialops/support/webhook/generic-webhook \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"event_id":"evt_1","thread_id":"conversation-42","subject":"Customer question","text":"When will this ship?"}'

Accepted events return 202 with thread_id and message_id. Duplicate events return duplicate: true. A 202 reply means the outbound action was queued; provider delivery still requires the provider adapter and its delivery result.

TRON Energy Lease

Energy must be between 1000 and 5000000, and duration between 300 and 86400 seconds. Get a quote first, then create the order with quote_id and max_cost. Order creation normally returns 202 pending; only provider fulfillment and chain verification represent success. Idempotency-Key prevents duplicate charging for the same tenant and lease parameters. Sandbox responses are dry_run and do not charge or create a real order.

Widget and files

Widgets use a separate widget-session JWT:

http
POST /v1/apps/bot/widget/auth
POST /v1/apps/bot/widget/token

Public file download/share routes are:

text
GET /v1/apps/file/download/*
GET /v1/apps/file/share/*

Authenticated file upload/list routes are PUT /v1/apps/file/upload and GET /v1/apps/file/list; API keys require the file:read scope. The canonical upload uses raw bytes and optional x-filename, x-expires-in, and x-max-downloads headers. The gateway accepts POST upload for legacy clients.

The stable Bot API includes chat history, replies, message edit/delete, file messages, interactions, actions, and the main send endpoint under /v1/apps/bot/*; API keys require bot:send. Widget, webhook, and WebSocket endpoints remain separate session/provider protocols and are documented in their service guides.

OpenAPI contract

Download the versioned OpenAPI 3.1 contract for the gateway, AI, Webhook Push, TRON, SocialOps, Bot, and File endpoints. It can be imported into Swagger UI, Postman or an SDK generator. The contract intentionally excludes widget/webhook/WebSocket session protocols because those use provider-specific authentication and upgrade semantics.

Health Check

bash
curl https://api.open4x.com/health

Expected response:

json
{
  "status": "ok",
  "db": "ok",
  "ts": "2026-06-19T14:48:46.146Z"
}