Elaichi is an agent platform for companies. It connects the software a company already runs on and lets agents work in those systems, inside the same permissions the company already grants its people. One org-wide MCP endpoint, authorized with OAuth, serves Claude, ChatGPT, Cursor and any other MCP client. MCP is how the product is delivered, not what the product is.
Skill routing
| The question is about | Load |
|---|---|
| What Elaichi is, where a screen lives, what a word means, what a plan includes | elaichi |
| Acting through an attached Elaichi MCP server | elaichi-mcp |
| Setting up Claude, ChatGPT, Cursor or another MCP client | elaichi-clients |
| Connecting accounts, connection health, transfer, offboarding, custom connectors | elaichi-connections |
| Toolboxes, templates, frozen params, delegation, synthetic tools | elaichi-toolboxes |
| Automations and workflows: triggers, steps, approvals, runs, sessions, webhooks | elaichi-automations |
| Collections, dashboards and public links, knowledge bases | elaichi-data |
| Roles, permissions, restrictions, access requests, audit, SSO, SCIM | elaichi-governance |
Writing code against api.elaichi.ai |
elaichi-api |
URLs
| What | Where |
|---|---|
| API | https://api.elaichi.ai |
| MCP endpoint | https://api.elaichi.ai/mcp (POST only) |
| Console | https://app.elaichi.ai |
| Public dashboard link | https://app.elaichi.ai/p/<token> |
| Docs | https://elaichi.ai/docs |
| OpenAPI | https://api.elaichi.ai/schema/openapi.json (also /schema/openapi.yml, /openapi.json, /openapi.yaml) |
| Support | support@elaichi.ai |
Paths are unversioned. The API host is the same for every data region. If an endpoint is not in the OpenAPI schema, it is internal and its URL is not a stable contract.
Authentication
Authorization: Bearer elch_{org_id}_{64 hex}
X-Organization-Id: org_… # optional with a token, which already names an org
Create a token at Settings → API tokens. The raw value is shown once.
A token has no scopes. It acts as the person who created it, with their
live permissions — change their role and the token changes with them; remove
them and it stops working. It is bound to one organization: naming a different
one in X-Organization-Id is refused. Server-side only.
An API token is the whole programmatic surface. The console's own browser sign-in is not an integration path, there is no supported way to obtain a session outside the browser, and nothing should try to mint or replay one.
Some actions need a person signed in at app.elaichi.ai, and a token gets
403 ("This action needs an interactive browser session"), not 428: the
whole /api-token router, SCIM tokens, approving an MCP client, managing
connected apps, resolving access requests, deleting the organization. A
signed-in person doing a sensitive change may also be asked to confirm it is
them ("Confirm it's you"), and the API answers 428 step_up_required until
they do. Either way, the right response is to send the person to the app.
Over MCP and in the Elaichi Agent there is no way to confirm it is you, so these operations are refused there and must be done in the app: sending, resending or revoking an invite, changing a member's roles, removing a member, deleting a role or a team, and revoking an API token. Listing or renaming API tokens works in the Elaichi Agent but is not offered over MCP at all.
MCP clients use neither: they sign in with OAuth and the person approves scopes on Elaichi's own consent screen.
The list envelope
Every endpoint returning an array:
{ "result": [], "next_cursor": "opaque-or-null", "prev_cursor": "opaque-or-null" }
limit— default 50, max 200 (above is clamped; below 1 is an error).cursor— opaque, round-trip untouched.q— server-side search, up to 200 characters, applied before paging, on the routes that document it.
Page until next_cursor is null. Never search, filter, sort or count
client-side over a paginated list — you would be operating on the pages you
happen to hold. Counts come from the server.
The one exception is /scim/v2/*, which speaks RFC 7644's
startIndex/count because the spec requires it.
Errors
{ "error": { "message": "…", "code": "…", "details": { } } }
| Code | HTTP |
|---|---|
validation_error, bad_request, missing_organization_header |
400 |
unauthorized |
401 |
permission_required, forbidden |
403 |
subscription_required (no active Gold or Black plan), feature_not_available (not on this plan), organization_blocked (suspended by Elaichi support), mfa_setup_required (the org requires two-factor) |
403 |
automations_not_available, automations_disabled, automations_kill_switch (the automation platform is not offered, turned off, or paused) |
403 |
not_found |
404 |
conflict |
409 |
payload_too_large |
413 |
step_up_required |
428 |
rate_limited |
429 |
internal_error |
500 |
platform_mcp_stopped, platform_connections_stopped, platform_signups_stopped (paused for maintenance; retry in a few minutes) |
503 |
clove_timeout, saffron_timeout (an internal service did not answer in time; retry) |
504 |
Many routes use a more specific code than these; branch on the ones you know and show the message for the rest.
permission_required carries details.required_permissions — any one of
them suffices. Show error.message unchanged; it is written for a person and
already names the permission.
A 404 may mean "not yours". Resources you hold no grant on are concealed
rather than refused.
X-Request-Id is exposed on responses. Log it.
Rate limits
| Surface | Limit |
|---|---|
| REST API | 600 requests / 60s per API token |
| MCP endpoint | 120 requests / 60s per OAuth access token |
429 carries Retry-After in seconds. There is no public idempotency key.
URL shape
| Kind | Pattern |
|---|---|
| CRUD | GET/POST /resource, GET/PATCH/DELETE /resource/:id |
| Action | POST /resource/:id/<verb> — verify, reconnect, execute, transfer, test |
| Nested collection | /resource/:id/<collection>[/:childId] |
Fields update through PATCH /resource/:id with an all-optional body; null
clears. There are no attribute subpaths. Unbounded child collections are
never embedded in a parent row — the row carries a count and a preview, and
the collection has its own paginated endpoint.
Capability fields
Authorization is computed server-side and shipped as a field. Read it; do
not re-derive it from owner_user_id and a permission list.
can_use · can_share · can_manage · can_transfer · can_revoke_share ·
can_see_shares, plus a few resource-specific ones such as can_reconnect
and can_delete.
can_manage is the single name for "may mutate this resource's settings"
across every resource type — there is no can_edit or can_update.
access_via (owner / direct / team / org) reports how you reach a
resource, and is omitted when nothing does.
Ids
TypeIDs: {prefix}_{26 chars}, a UUIDv7 in lowercase Crockford base32.
Opaque, but chronologically sortable within a prefix.
| Prefix | Entity | Prefix | Entity |
|---|---|---|---|
org |
organization | conn |
connection |
usr |
user | tbx |
toolbox |
mem |
org membership | tbxe |
toolbox entry |
dom |
org domain | tpl |
template |
team |
team | tple |
template entry |
tmem |
team membership | syn |
synthetic tool |
inv |
invite | acl |
sharing grant |
role |
role | rstr |
restriction |
mrol |
member role | areq |
access request |
atok |
API token | aud |
audit event |
sso |
SSO connection | gmap |
group mapping |
stok |
SCIM token | sgrp |
SCIM group |
ocli |
OAuth client | ogrt |
OAuth grant |
ldst |
logging destination | onfd |
notification destination |
pkey |
passkey | skv |
toolbox or template skill version |
file |
stored tool file | fupr |
file upload request |
crun |
one run_code execution |
req |
request id (logs and audit) |
The automation platform adds:
| Prefix | Entity | Prefix | Entity |
|---|---|---|---|
auto |
automation | aver |
published automation version |
arun |
automation run | ases |
automation session |
aapr |
approval | awh |
webhook endpoint |
coll |
collection | crow |
collection row |
cfld |
collection field | dash |
dashboard |
pdl |
public dashboard link | kbs |
knowledge base |
kdoc |
knowledge document | bndl |
app (bundle) |
bver |
app version | bins |
app install |
ulim |
usage limit | pcap |
payment cap |
Connectors are identified by slug, not by a TypeID. A collection's extra table is named by its key on the wire.
Three secrets embed an org id instead of being TypeIDs — elch_… (API token),
einv_… (invite), escim_… (SCIM token), all shaped
{prefix}_{orgId}_{secret}. The org id routes the request; the secret is only
ever compared as a hash. A public dashboard link's token starts epd_.
Dynamic toolbox ids are computed and read-only: global:{userId} (named
All tools) and connection:{connectionId}.
MCP: namespaces and scopes
| Namespace | Cost |
|---|---|
elaichi__* — administers Elaichi itself |
One internal call. Cheap. |
Connected tools, reached via execute_tool or run_code |
Restriction check + credential fetch + live third-party call. Seconds, and the vendor's rate limit. |
elaichi__toolbox__execute and elaichi__synthetic_tool__execute cost the
second kind, because they run a connected tool. Operation a.b in Elaichi's
catalog is MCP tool elaichi__a__b.
Connected tools are never listed in tools/list, however few there are.
search_tools finds one; execute_tool runs it; run_code runs a small
JavaScript program that calls several. search_tools indexes connected tools
only — never an elaichi__ operation. A synthetic tool appears in it only as
an entry of a toolbox you can reach; a standalone one is listed by
elaichi__synthetic_tool__list and run by elaichi__synthetic_tool__execute.
Ranking is lexical: query with concrete tool-ish words, not a sentence.
When the person reaches no connected tool at all, search_tools and
execute_tool are not listed. Clients that support MCP Apps also get
show_card and show_object, which only draw cards.
A tool reaching several accounts of one app takes a required connection
argument — one of its schema enum labels, copied exactly, up to 12 accounts;
an exact account name or conn_… id beyond that. Never guess it. Ask.
| Scope | Consent screen label | Covers |
|---|---|---|
mcp:read |
Read your organization's data | Reads, including sensitive ones like the audit log |
mcp:write |
Create and change data | Creates and changes; implies read |
mcp:destructive |
Delete data and remove access | Deletes, removals, revokes; implies read. Never pre-ticked |
mcp:tools |
Run your connected tools | Connected tools, from all of them or chosen toolboxes. A tool that deletes needs mcp:destructive too |
openid, email |
Confirm who you are, See your email address | Identity only; reach nothing in the organization |
A client that asks for no scope gets mcp:read only. A grant's scopes never
change after consent; adding one means connecting the client again.
Two rules that hold everywhere
- Nothing in Elaichi takes a credential from a model. No tool or operation accepts a password, API key or token as input, and none returns one. A flow that seems to need one really needs a connect link the person opens themselves.
- A refusal names what it wanted, and that is the end of it. Relay the permission or scope and stop. Reaching the same data another way is the failure mode the whole layer exists to prevent.