API Reference
Open4X API base:
https://api.open4x.comApplication services are mounted under:
/v1/appsAuthentication
API Key
Use API keys for server-side integrations:
X-API-Key: sk_xxxAPI keys are shown once and stored as hashes.
JWT
Use JWT for console-authenticated workflows:
Authorization: Bearer <token>Do not hardcode JWTs in long-running production services.
Registration and login
POST /auth/register
POST /auth/login
POST /auth/refresh
POST /auth/logoutLogin returns token and refresh_token. Refresh tokens rotate and the previous token becomes invalid after use.
Recommended Scopes
| Scope | Capability |
|---|---|
bot:send | Send bot messages and replies. |
bot:session:create | Create a short-lived Widget Session. |
bot:events:read | Read incremental Widget conversation events. |
file:read | Read or download files. |
push:send | Send Webhook Push messages. |
tron:lease | Quote, create, and query TRON energy lease orders. |
jobs:read | Read tenant-owned asynchronous job status. |
ai:invoke | Call AI chat, streaming, and embeddings endpoints. |
socialops:write | Ingest SocialOps webhook events. |
socialops:read | Read 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:
{
"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:
{
"error": "Insufficient balance"
}Current endpoints still use mostly flat errors with service-specific fields:
{
"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:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request body or parameters are invalid. |
| 401 | unauthorized | Missing or invalid credentials. |
| 402 | insufficient_balance | The account does not have enough balance. |
| 403 | forbidden | The key or user is missing required permission. |
| 404 | not_found | The resource was not found. |
| 409 | Conflict | max_cost, expired quote, or AI idempotency conflict. |
| 422 | Business validation failed | Defined by the individual service. |
| 429 | Rate limited | Retry with backoff. |
| 502 | Upstream failed | Provider or bound service failed. |
Service Endpoints
| Service | Endpoint |
|---|---|
| AI chat | POST /v1/apps/ai/:alias/chat |
| AI streaming | POST /v1/apps/ai/:alias/chat/stream |
| AI embeddings | POST /v1/apps/ai/:alias/embeddings |
| OpenAI-compatible AI | GET /v1/apps/ai/:alias/v1/models, POST /v1/apps/ai/:alias/v1/chat/completions, POST /v1/apps/ai/:alias/v1/embeddings |
| Webhook Push | POST /v1/apps/webhook-push/:alias/send |
| TRON quote | POST /v1/apps/tron/quote |
| TRON lease | POST /v1/apps/tron/lease |
| TRON lease list | GET /v1/apps/tron/leases |
| TRON lease detail | GET /v1/apps/tron/leases/:id |
| Async job list | GET /v1/jobs |
| Async job status | GET /v1/jobs/:id |
| SocialOps webhook | POST /v1/apps/socialops/:alias/webhook/:provider |
| SocialOps provider capabilities | GET /v1/apps/socialops/providers |
| SocialOps inbox | GET /v1/apps/socialops/:alias/inbox |
| SocialOps threads | GET /v1/apps/socialops/:alias/threads |
| SocialOps thread messages | GET /v1/apps/socialops/:alias/threads/:thread_id/messages |
| SocialOps reply queue | POST /v1/apps/socialops/:alias/threads/:thread_id/replies |
| File upload | PUT /v1/apps/file/upload |
| File list | GET /v1/apps/file/list |
| File download/share | GET /v1/apps/file/download/*, GET /v1/apps/file/share/* |
| Bot send | POST /v1/apps/bot |
| Bot chats/messages | GET /v1/apps/bot/chats, GET /v1/apps/bot/chats/:chatId/messages |
| Bot replies | POST /v1/apps/bot/messages/reply |
| Bot interactions | `GET |
| Widget Session | POST /v1/apps/bot/widget/auth, POST /v1/apps/bot/widget/refresh, POST /v1/apps/bot/widget/revoke |
| Widget aggregate events | GET /v1/apps/bot/widget/events?cursor=...&limit=100 |
Creating API keys and services
Console endpoints use a JWT:
POST /console/keys
POST /console/keys/{id}/rotate
DELETE /console/keys/{id}
POST /console/servicesCreate an API key:
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:
GET /v1/jobs/job_xxxStatuses 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:
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
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:
X-OpenEdge-Estimated-Cost: 0.0005
X-OpenEdge-Final-Cost: 0.0005
X-OpenEdge-Total-Tokens: 120Streaming 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
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:
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:
POST /v1/apps/bot/widget/auth
POST /v1/apps/bot/widget/tokenPublic file download/share routes are:
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
curl https://api.open4x.com/healthExpected response:
{
"status": "ok",
"db": "ok",
"ts": "2026-06-19T14:48:46.146Z"
}