REST API
REST API Developer-facing API for programmatic access to app skills and focus modes. For full request/response schemas, see the OpenAPI docs. Why This Exists...
REST API
Developer-facing API for programmatic access to app skills and focus modes. For full request/response schemas, see the OpenAPI docs.
Why This Exists
Provides programmatic access to OpenMates app skills for automation, integrations, and the CLI package. The REST API cannot decrypt/encrypt chats (zero-knowledge architecture) – use the CLI/SDK package for chat operations. See CLI Package.
How It Works
Base URL and Authentication
https://api.openmates.org/v1
Authorization: Bearer YOUR_API_TOKEN
API keys can be scoped to specific apps/skills. Write operations via API do not require user confirmation (unlike the web app) – security comes from key scopes, rate limiting, and logging. See Action Confirmation.
Unified Endpoint Pattern
All app skill endpoints follow:
POST /v1/apps/{app_id}/skills/{skill_id}
Examples: POST /v1/apps/web/skills/search, POST /v1/apps/videos/skills/get_transcript, POST /v1/apps/images/skills/generate
Anonymous CLI Skill Calls
The official cloud also accepts logged-out CLI calls at
POST /v1/anonymous/apps/{app_id}/skills/{skill_id} with a stable
X-OpenMates-Anonymous-ID header. The body uses the same skill schema, with
exactly one provider request per call. Provider rate-limit waits are rejected
instead of queued. Only skills explicitly marked
anonymous_access: inline in app.yml are eligible: they return an immediate
result, require no connected account, and create no file or background job.
File generation, uploads, account actions, and private chat/embed references
require authentication.
The server quotes and reserves credits before dispatch against the same per-identity and server-wide daily, weekly, and monthly anonymous limits used by web and CLI chat. When a limit is exhausted, anonymous work returns 429; authenticated users with account credits continue through the authenticated endpoint. Anonymous calls pass no user, chat, message, embed, or upload context to skills and create no permanent Directus content records. The anonymous route is intentionally outside the authenticated developer OpenAPI schema.
GET /v1/anonymous/free-usage/status?anonymous_id=<guest-id> returns the
guest’s daily_remaining_percent as a whole number from 0 to 100. It uses the
tightest remaining daily allowance across the local identity, IP identity, and
shared daily pool. Without a guest ID, the percentage is null. The public
response does not expose credit counts or identity hashes; can_send_text and
reason also reflect weekly and monthly limits.
Auto-Registration
REST routes are auto-registered per discovered app at api startup by register_app_and_skill_routes() in apps_api.py. There is no manual registration step and no hardcoded app/hostname map.
Discovery flow (in-process since OPE-342):
discover_apps()in main.py delegates tobuild_skill_registry()in skill_registry.py.build_skill_registry()filesystem-scansbackend/apps/*/app.yml, applies feature availability filtering, and instantiates aBaseApp(register_http_routes=False)per app. EachBaseAppresolves every skillclass_pathviaimportlib.- The result is published as
app.state.skill_registry(and as a process-global singleton for code paths without FastAPI app context). register_app_and_skill_routes()registersGET /v1/apps/{id},GET /v1/apps/{id}/skills/{skill_id}, andPOST /v1/apps/{id}/skills/{skill_id}for every loaded app.call_app_skill()dispatches viaSkillRegistry.dispatch_skill()— directly in-process, no HTTP to sibling containers.
To add a new app: drop a folder under backend/apps/, restart api. There is no docker-compose.yml edit step. If a skill’s class_path import fails at startup, BaseApp._resolve_skill_classes logs an ERROR and the skill returns 404 — the rest of the app and the api itself stay up.
Request Format
{ "requests": [{ /* skill-specific parameters */ }] }
Up to 5 parallel requests per call. Each spawns a separate Celery task. Rate limits tracked per provider/skill/model via Dragonfly cache counters.
Response Patterns
Quick-executing skills (e.g., web search): returns results directly with previews array.
Long-running skills (e.g., image generation): returns task_id + embed_id. Poll via GET /v1/tasks/{task_id}. Download files via GET /v1/embeds/{embed_id}/file?format=preview|full|original.
Focus Modes
Activated via the chats endpoint (chat IDs in body, never URL, for privacy):
POST /v1/chats
{ "chat_id": "chat_abc123", "focus_mode_on": "web.research" }
Deactivate: { "chat_id": "chat_abc123", "focus_mode_off": true }
Error Handling
Standard HTTP status codes (200, 400, 401, 403, 404, 429, 500, 503). Error response:
{ "error": { "code": "INVALID_PARAMETER", "message": "...", "details": {} } }
Rate Limiting
- Per-user limits based on subscription tier
- Max 5 parallel requests per skill call
- Provider API rate limits tracked per provider/skill/model
- Tasks queued (not rejected) when limits reached, auto-retry on reset
- Headers:
X-RateLimit-Remaining,X-RateLimit-Reset
Privacy
- Chat IDs always in body, never URLs
- Minimal data transfer
- Client-side encryption (REST API cannot decrypt chats)
- No tracking or profiling
Direct use from the Apps workspace
The public web workspace uses /#apps/<app>/<skill> URLs. Compact /apps/...
paths and previous Settings Apps links forward to the corresponding hash URL;
URL hyphens resolve to the catalog’s underscore identifiers.
App details separate Skills, Focus modes, Memories, Embeds and Workflows;
skill details retain Overview, Embeds and Workflows.
GET /v1/apps/{app_id}/skills/{skill_id}/details lazily supplies the direct
request schema, declared defaults, primary field paths, pricing, providers,
models and execution availability. sdk_tool_schema takes precedence over the
assistant schema. The shared schema renderer projects at most two primary
controls and puts additional fields in Show settings. Required input is
validated without inserting workflow test examples as defaults.
Optional x-ui.apps hints override presentation on a copied Apps schema only.
For example, Audio uses a prompt textarea while optional duration and format
fields appear in Show settings; its original Workflow hints and defaults remain
unchanged.
Execution continues through the existing /v1/apps/.../skills/... endpoints.
First-party Team execution supplies an authorized team_id query parameter;
the server checks membership and spending role and preserves that billing
context through asynchronous jobs. CLI requests retain their existing
temporary-history behavior. Guests use the existing anonymous direct endpoint
after a request-specific /availability quote; execution still performs atomic
budget admission and rejects background, storage-dependent and account-dependent
skills.
The browser encrypts Apps results before posting a chatless root and its children
to /v1/apps/workspace/results. expected_user_id binds the upload to the
submitting user; Team key wrappers, app indexes and creator ownership are checked
before writes. Existing generated asset IDs are reused. The paginated result
catalog also indexes saved chat and memory embeds using server-verified account
context. Saved workflows are selected by app references in their current graph,
including workflows that have not run.
Older chat embeds are classified in the browser. While an authenticated Embeds
tab is open, resumable discovery pages scoped chat metadata and requests at most
50 encrypted root embeds from one verified chat at a time, without message
histories or media bytes. The existing content-batch WebSocket request accepts
the opt-in apps_legacy_embeds_only flag. Classification preserves original key
wrappers and posts app projections in batches of at most 50. Bulk sync also
notifies an already-open library to revisit newly received roots. Leaving the
library or switching account stops discovery and guards pending local writes.
Guest graphs remain encrypted in IndexedDB and are promoted into Personal storage after signup without another skill execution. Failed or interrupted promotion keeps the local source. Retained task IDs allow processing results to resume polling after reload. Generated media obtains a fresh download URL through the session-authenticated generated-assets endpoint after owner or Team membership verification.
Implementation: apps_workspace_metadata.py, apps_workspace_results_service.py,
AppsWorkspace.svelte, AppsSkillForm.svelte, appsWorkspaceService.ts and
appsWorkspaceResultsService.ts. Product intent is recorded in
feature.apps-workspace@1 and feature.workspace-shell@3.
Related Docs
- Function Calling – LLM tool integration
- CLI Package – SDK with chat encryption support
- Action Confirmation – confirmation flow differences
- OpenAPI Docs – auto-generated interactive reference