External API
Read and write a workspace's data from another system. Every workspace exposes
the same data through four transports, all at your workspace URL and all
authenticated by a per-user API key. New integrations use the JSON API
(POST /json/2/...); AI agents use the MCP server (POST /mcp), which is
the one surface that adds write guardrails and an audit trail on top of your
permissions. The classic XML-RPC and JSON-RPC endpoints stay supported for
existing clients.
Which transport should I use?
| Transport | Path | Auth | Use it for |
|---|---|---|---|
| JSON API | POST /json/2/<model>/<method> |
Bearer key | Scripts and server-to-server integrations. Preferred. |
| MCP | POST /mcp |
Bearer key | AI agents. See AI agents & MCP. |
| XML-RPC | /xmlrpc/2/common, /xmlrpc/2/object |
Login + key as password | Legacy clients. |
| JSON-RPC | POST /jsonrpc |
Login + key | Legacy clients. |
Guardrails live on /mcp only
The write denylist, confirmation gating, secret masking, and audit log
apply to the MCP server. The JSON API and the classic RPC endpoints are
bounded by the connected user's access rights and record rules, and nothing
more. A key with wide permissions can write anything that role can write
over /json/2. Point scripts at a scoped
integration user, and point agents at /mcp.
Authenticate with an API key
Use a dedicated integration user, not a person's login. Sign in as that user and mint a key from its profile: Profile Settings → API & AI Agent → New API Key (on workspaces with agents enabled), or Profile Settings → Account Security → New API Key otherwise. The key is shown once. Every call is bounded by that user's access rights, so grant the integration user only the roles it needs.
- Base URL —
https://<workspace>.everjust.app - Database — the workspace's name (its subdomain), passed on classic calls.
Call the JSON API
POST /json/2/<model>/<method> with the key as a bearer token and a JSON body.
The body carries the method's keyword arguments; the response is the raw return
value. Stateless, JSON in and out.
Read with search_read:
curl -X POST https://acme.everjust.app/json/2/crm.lead/search_read \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": [["stage_id.name", "=", "New"]],
"fields": ["name", "email_from"], "limit": 20}'
Create with vals_list (a list of records to create):
curl -X POST https://acme.everjust.app/json/2/crm.lead/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vals_list": [{"name": "Globex", "email_from": "buyer@globex.com"}]}'
Two rules to remember:
- Record methods take
ids; model-level methods omit it. A method that operates on existing records (read,write,unlink,action_confirm) receives the target ids under"ids". A method that operates on the model itself (create,search,search_read,name_search) has noids. - Only public methods are callable. Requests route through the platform's
public-method gate, so private methods (underscore-prefixed, or marked
private) return an access error. This is the same gate the MCP
calltool uses.
Explore the API from your workspace
Your workspace serves an interactive schema browser at GET /doc — the models,
fields, and methods reachable with your key, on this workspace's actual
installed apps. It is the fastest way to confirm a model name or field before
you write a call. What you see there is what your key can reach.
Classic XML-RPC / JSON-RPC
Still supported for existing clients and every standard library that speaks the protocol. New integrations use the JSON API above.
import xmlrpc.client
url = "https://acme.everjust.app"
db = "acme" # the workspace subdomain
key = "YOUR_API_KEY" # from the integration user's profile
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
uid = common.authenticate(db, "integration@acme.com", key, {})
models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")
ids = models.execute_kw(db, uid, key,
"crm.lead", "search", [[["stage_id.name", "=", "New"]]])
records = models.execute_kw(db, uid, key,
"crm.lead", "read", [ids], {"fields": ["name", "email_from"]})
new_id = models.execute_kw(db, uid, key,
"crm.lead", "create", [{"name": "Globex"}])
Practical notes
- One database per workspace. The
dbis the workspace's database name (its subdomain). A key only ever reaches its own workspace. - Scope the user. The key inherits its user's permissions. Give a script a user with the roles it needs and no more.
- Rate and batch. Prefer
search_readoversearch+read, and batch writes into one call.
Related
- AI agents & MCP — the guarded, audited surface over this same API, for Claude Code and Codex.
- Integration & agent users — provision the scoped account your key should belong to.
- The methods you call are standard model methods —
search_read,read,create,write,unlink, and any public method a model exposes.
Last reviewed: 2026-07-21
Need a hand with this? company@everjust.co — a human answers.