# JUXA.IO CRM API Reference # For product overview: /llms.txt # For complete product documentation, features, and data model: /llms-full.txt # Base URL: set BASE_URL to your deployment origin (e.g. https://juxa.io or http://localhost:3077) # Auth: Bearer token, Authorization: Bearer oc_sk_ # Create API keys at Settings > API Keys in the web UI # OpenAPI spec: /openapi.json # OpenAPI subset for automation / JSON tools (no SSE chat): /openapi-llm-tools.json — see docs/llm-universal-integration.md in repo ## Public API v1 (stable integration surface) These paths are the **supported integration surface** for third-party systems, MCP servers, and automation. Other `/api/v1/*` routes may exist for the web app or may change; prefer this set for external contracts. | Area | Methods | Path pattern | |------|---------|--------------| | Objects & schema | GET | `/api/v1/objects`, `/api/v1/objects/:slug`, `/api/v1/objects/:slug/attributes` | | Records | GET, POST, PATCH, DELETE | `/api/v1/objects/:slug/records`, `/api/v1/objects/:slug/records/:recordId` | | Record query (read) | POST | `/api/v1/objects/:slug/records/query` | | Related / activity | GET | `/api/v1/objects/:slug/records/:recordId/related`, `.../activity`, `.../tasks`, `.../notes` | | Search | GET | `/api/v1/search`, `/api/v1/records/browse` | | Lists | GET, POST, PATCH, DELETE | `/api/v1/lists`, `/api/v1/lists/:listId`, `.../entries`, etc. | | Notes | GET, POST, PATCH, DELETE | `/api/v1/notes`, `/api/v1/notes/:noteId` | | Tasks | GET, POST, PATCH, DELETE | `/api/v1/tasks`, `/api/v1/tasks/:taskId` | | Workspace (read) | GET | `/api/v1/workspace` | | Juxa Docs handoff | POST | `/api/v1/integrations/juxa-docs-handoff` — mint one-time link; **consume** (browser session only): `POST /api/v1/integrations/juxa-docs-handoff/:token/consume` | | Workspace app marketplace (session) | GET, POST | `/api/v1/apps/marketplace` — list or publish catalog entries (admin); **not** available with `oc_sk_` | | Ecosystem builder entitlement (session) | GET, POST | `/api/v1/workspaces/:workspaceId/ecosystem-builder-entitlement` — historial o solicitud `pending` para autorización de plataforma (admin workspace); ver `docs/public/ecosystem-apps-developers.md` | | App installations (session) | GET, POST | `/api/v1/apps/installations` — list or install; **not** `oc_sk_` | | Uninstall app (session) | DELETE | `/api/v1/apps/installations/:installationId` — admin | | Mint embed token (session) | POST | `/api/v1/apps/installations/:installationId/embed-sessions` — returns short-lived `oc_embed_...` (do not ship `oc_sk_` in public embeds) | | Embed token auth | Bearer | `Authorization: Bearer oc_embed_...` — narrow scopes from app manifest; chat + CRM reads/writes + optional `POST /api/v1/apps/osint-research` | | Agent missions (orquestación, expediente) | GET, POST | `/api/v1/agent-missions` — optional JSON body `workspaceAppInstallationId` (UUID) to attribute usage to a **workspace app installation** (see Settings → Workspace apps); invalid id → 400 | **API key scopes** (optional JSON array on create; default `["*"]`): - `*` — full access (same as before scopes existed). - `integrations:read` — GET/HEAD on the read paths above plus `POST .../records/query` only. - `integrations:write` — read paths plus create/update/delete on objects’ **records**, tasks, notes, and lists (not object schema, not workspace settings, not billing/recovery/ERP). - `integrations:assistant` — **Legal assistant chat** only under `/api/v1/chat`: `GET|POST /api/v1/chat/conversations`, `GET|DELETE /api/v1/chat/conversations/:id`, `GET /api/v1/chat/conversations/:id/ai-usage` (persisted per-round usage: tokens, credits, extras; optional `?limit=`), and `POST /api/v1/chat/completions`. Combine with `integrations:read` / `integrations:write` for MCP or agents that need **CRM data + in-workspace legal reasoning** (see MCP package `@juxa/mcp-juxa-crm`). - `integrations:handoff` — **Mint** `POST /api/v1/integrations/juxa-docs-handoff` only (opens `/docs` with composer/doc payload in the browser; does not grant CRM record writes). `integrations:write` and `*` also allow mint. Rate limit: Bearer API keys are limited per key (default **300 requests/minute** per process; set `API_KEY_RATE_LIMIT_PER_MINUTE=0` to disable). Use Redis or a gateway for multi-instance limits. ## Quick Start ```bash # Set your instance URL and API key export BASE_URL="https://your-instance.example" export JUXA_API_KEY="oc_sk_..." # List all object types curl -H "Authorization: Bearer $JUXA_API_KEY" "$BASE_URL/api/v1/objects" # List people records curl -H "Authorization: Bearer $JUXA_API_KEY" "$BASE_URL/api/v1/objects/people/records" # Create a person curl -X POST "$BASE_URL/api/v1/objects/people/records" \ -H "Authorization: Bearer $JUXA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"values": {"name": {"first_name": "Jane", "last_name": "Doe"}, "email_addresses": "jane@example.com"}}' # Search across all records curl -H "Authorization: Bearer $JUXA_API_KEY" "$BASE_URL/api/v1/search?q=Jane" # Query with filters curl -X POST "$BASE_URL/api/v1/objects/deals/records/query" \ -H "Authorization: Bearer $JUXA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filter": {"operator": "and", "conditions": [{"attribute": "deal_value", "operator": "gte", "value": 10000}]}, "sorts": [{"attribute": "deal_value", "direction": "desc"}]}' ``` ## Response Format All responses use envelope format: - Success: `{ "data": }` - Error: `{ "error": { "code": "ERROR_CODE", "message": "description" } }` - Paginated: `{ "data": { "records": [...], "pagination": { "limit": 50, "offset": 0, "total": 123 } } }` ## Endpoints Use `$BASE_URL` as your deployment origin (same host you use in the browser). Paths are relative to that origin. ### Objects GET /api/v1/objects :List all objects POST /api/v1/objects :Create object {slug, singularName, pluralName, icon?} GET /api/v1/objects/:slug :Get object with attributes PATCH /api/v1/objects/:slug :Update object DELETE /api/v1/objects/:slug :Delete object (admin) ### Attributes GET /api/v1/objects/:slug/attributes :List attributes POST /api/v1/objects/:slug/attributes :Create attribute {slug, title, type, config?, isRequired?, isUnique?, isMultiselect?, options?, statuses?} PATCH /api/v1/objects/:slug/attributes :Update attribute {attributeId, ...fields} DELETE /api/v1/objects/:slug/attributes?attributeId= :Delete attribute ### Attribute Options GET /api/v1/objects/:slug/attributes/options?attributeId= :List options POST /api/v1/objects/:slug/attributes/options :Create option {attributeId, title, color?, isActive?} PATCH /api/v1/objects/:slug/attributes/options :Update option {optionId, attributeType, ...fields} DELETE /api/v1/objects/:slug/attributes/options?optionId=&attributeType= :Delete option ### Records GET /api/v1/objects/:slug/records?limit=50&offset=0 :List records (paginated) POST /api/v1/objects/:slug/records :Create record {values: {attr_slug: value}} GET /api/v1/objects/:slug/records/:recordId :Get single record PATCH /api/v1/objects/:slug/records/:recordId :Update record {values: {attr_slug: value}} DELETE /api/v1/objects/:slug/records/:recordId :Delete record ### Record Query (filter + sort) POST /api/v1/objects/:slug/records/query Body: {limit?, offset?, filter?: FilterGroup, sorts?: SortConfig[]} Filter: {operator: "and"|"or", conditions: [{attribute, operator, value}]} Operators: equals, not_equals, contains, not_contains, starts_with, ends_with, is_empty, is_not_empty, gt, gte, lt, lte Sort: {attribute: "slug", direction: "asc"|"desc"} Assert mode: {mode: "assert", matchAttribute, matchValue, values}:find-or-create ### Record Relations & Activity GET /api/v1/objects/:slug/records/:recordId/related :Related records GET /api/v1/objects/:slug/records/:recordId/activity :Activity feed GET /api/v1/objects/:slug/records/:recordId/tasks :Tasks linked to record GET /api/v1/objects/:slug/records/:recordId/notes :Notes on record POST /api/v1/objects/:slug/records/:recordId/notes :Create note {title?, content?} ### Bulk Import POST /api/v1/objects/:slug/records/import :Import up to 1000 records {rows: [{attr_slug: value, ...}]} ### Lists GET /api/v1/lists :List all lists POST /api/v1/lists :Create list {name, objectSlug, isPrivate?} GET /api/v1/lists/:listId :Get list PATCH /api/v1/lists/:listId :Update list DELETE /api/v1/lists/:listId :Delete list ### List Attributes GET /api/v1/lists/:listId/attributes :List list-specific attributes POST /api/v1/lists/:listId/attributes :Create list attribute {slug, title, type} PATCH /api/v1/lists/:listId/attributes :Update list attribute {id, ...fields} DELETE /api/v1/lists/:listId/attributes?id= :Delete list attribute ### List Entries GET /api/v1/lists/:listId/entries?limit=50&offset=0 :List entries (paginated) POST /api/v1/lists/:listId/entries :Add record to list {recordId, values?} PATCH /api/v1/lists/:listId/entries/:entryId :Update entry values {values} DELETE /api/v1/lists/:listId/entries/:entryId :Remove entry from list GET /api/v1/lists/:listId/available-records?search= :Records not in this list ### Notes GET /api/v1/notes?limit=50&offset=0 :List all notes (paginated) GET /api/v1/notes/:noteId :Get note PATCH /api/v1/notes/:noteId :Update note {title?, content?} DELETE /api/v1/notes/:noteId :Delete note ### Tasks GET /api/v1/tasks?showCompleted=false&limit=50&offset=0 :List tasks (paginated) POST /api/v1/tasks :Create task {content, deadline?, recordIds?, assigneeIds?} PATCH /api/v1/tasks/:taskId :Update task {content?, isCompleted?, deadline?} DELETE /api/v1/tasks/:taskId :Delete task ### Workspace GET /api/v1/workspace :Get workspace PATCH /api/v1/workspace :Update workspace (admin) {name?} ### Workspace Members GET /api/v1/workspace-members :List members POST /api/v1/workspace-members :Add member (admin) {email, role?} PATCH /api/v1/workspace-members/:memberId :Update role (admin) {role} DELETE /api/v1/workspace-members/:memberId :Remove member (admin) ### Notifications GET /api/v1/notifications?unreadOnly=false&limit=30 :List notifications PATCH /api/v1/notifications/:notificationId :Mark as read POST /api/v1/notifications/mark-all-read :Mark all as read ### Search GET /api/v1/search?q=query&limit=20 :Global search GET /api/v1/records/browse?limit=20 :Browse recent records ### API Keys (admin only) GET /api/v1/api-keys :List API keys POST /api/v1/api-keys :Create key {name, scopes?, expiresAt?} DELETE /api/v1/api-keys/:keyId :Revoke key ### AI Chat POST /api/v1/chat/completions :Send message (SSE streaming) {conversationId, message, legalAssistantMode?, juxaContext?, ...} POST /api/v1/chat/tool-confirm :Confirm/deny write tool {conversationId, toolCallId, approved} GET /api/v1/chat/conversations :List conversations GET /api/v1/chat/conversations/:id :Get conversation with messages DELETE /api/v1/chat/conversations/:id :Delete conversation ### Juxa Docs handoff (Chrome / Google Docs / Word → Juxa Docs) POST /api/v1/integrations/juxa-docs-handoff :Mint one-time handoff {composerDraft?, docPlain?, docHtml?, tabLabel?, openConstructor?, agentQuick?, embedGoogleDocs?, source?, sourceUrl?, excerptHash?}. `embedGoogleDocs: true` → `handoffUrl` opens **`/docs/embed?handoff=…&surface=google-docs`** (Google Docs sidebar + “Insertar en Google Doc”). Returns `{ id, handoffUrl, expiresAt }`. API key: scopes **`integrations:handoff`**, **`integrations:write`**, or **`*`**. Open `handoffUrl` in a browser where the **same user** is logged into the same workspace, then the app calls consume. POST /api/v1/integrations/juxa-docs-handoff/:token/consume :Session cookie only. Returns `{ payload }` (same shape as mint body + `v: 1`). Single-use; ~10 min TTL. **Bearer API keys (`oc_sk_`)** calling `POST /api/v1/chat/completions` must send **`legalAssistantMode: true`**. That mode uses the same workspace legal/CRM **system prompts** as the in-app assistant but **does not** register upstream gateway CRM tools (no tool-confirm loop). Keys need scope **`integrations:assistant`** or `*`. Cookie/session clients omit `legalAssistantMode` (in-app tool cards unchanged). Full machine-readable spec: `/openapi.json`.