List toolboxes visible to the caller
/toolbox
Toolboxes the caller owns plus every toolbox shared with them, each with the caller's `access_level`, PLUS the two virtual dynamic rows (`global:{user_id}`, one `connection:{connection_id}` per active usable connection) appended to the LAST page only (the one whose `next_cursor` is null), always `access_level: "owner"` — so the order across the whole listing is stored toolboxes first, automatic ones after. There is no org-wide oversight listing for toolboxes and no `?visibility=` parameter: no organization permission — not even `toolbox:view`/`toolbox:manage` — widens this beyond what the caller owns or was granted; a private toolbox never appears here for anyone but its owner and its grantees. `?q=` matches the name as a substring. `?type=stored` drops the dynamic rows entirely — for a caller, such as a toolbox picker, that only wants ACL-backed rows and would otherwise have to filter the dynamic ones out itself. `?connector_slug=` narrows to the toolboxes holding a tool from any of the named connectors; take the slugs worth passing from `GET /toolbox/connector`, not from the whole catalog. The three filters combine by AND. Cursor-paginated.
Query Parameters
Substring match (case-insensitive) on the toolbox name. LIKE wildcards are matched literally. Max 200 characters.
stored restricts the listing to ACL-backed toolboxes, omitting the dynamic global:…/connection:… rows; automatic is the mirror, paging the dynamic rows alone. Unset returns both, stored first. Any other value is a 400.
storedautomatic
Filter to the toolboxes holding at least one tool from ANY of these connectors — a union, never an intersection. Repeat the parameter or send one comma-separated value; both are the same filter, spelled exactly as GET /connection?connector_slug= is. At most 25 distinct values per request; more is a 400, never a silent truncation. A dynamic row is matched on the connectors it really spans: a connection:{id} row on its own connector, the global:{user_id} row when any of the caller's active usable connections matches. GET /toolbox/connector is where the values come from.
Response Body
The caller's access: owner, or a granted view/use/edit level. Always owner for a dynamic row. Never oversight — toolboxes have no org-wide oversight fallback.
Present on a GET /toolbox stored row exactly when that row's can_see_shares is true — the caller owns it, holds edit, or administers a team it is granted to. Absent from dynamic rows, and deliberately absent for a view/use grantee: the grantee set, counts included, is information about colleagues (docs/access-model.md §8). Read can_see_shares to distinguish "not permitted" from "not carried"; never infer it from this field's absence.
5 properties
Level of the org-wide grant, or null when there is none.
viewuseeditnull
At most 5 grantees, broadest first (org, then teams, then members), for a hover preview.
4 properties
userteamorg
viewuseedit
Display name; null for org grants and for grantees no longer in the org.
Every grant, the org-wide one included.
How the CALLER reaches this toolbox — not who else can. owner — they own it. direct — a grant naming them personally. team — a grant to a team they belong to (or, per §6.2, one they administer), named in access_via_team. org — an organization-wide grant. When several sources apply the BROADEST wins and the caller's access level is not consulted: owner, else org, else team, else direct. So a caller granted edit personally AND view org-wide reads org — a narrower grant must never mask org-wide exposure, since this field says how far the toolbox reaches, not what the caller may do with it. Deliberately NOT gated on can_see_shares, and deliberately not a widening of it: this is the caller's OWN grant and their OWN team memberships, so a view/use grantee receives it while the grantee list — information about colleagues — stays closed to them. No other member is ever named. ABSENT when nothing reaches the caller — a transfer answering the ex-owner of a resource that was never shared, or a catalog row browsed with no grant behind it. Absent means "no source to name", never "not permitted", and never an implied org.
ownerdirectteamorg
Present exactly when access_via is team, absent otherwise. The team the caller reaches this toolbox through — one of their own teams, never a disclosure about anybody else.
2 properties
Team id (team_…).
Team display name, or null when the team no longer resolves in the directory — the same null contract every resolved grantee name carries.
Whether the caller may edit this toolbox's own settings — owner, or edit access. No toolbox:manage fallback. Mirrors PATCH /toolbox/{id}.
can_share, verbatim — a THIRD formula, distinct from can_manage: an edit grantee whose role omits toolbox:share can edit the toolbox but was never meant to grant or revoke someone else's access to it. Mirrors DELETE /toolbox/{id}/share/{aclId}.
Whether the caller may share this toolbox — owner, edit access, or toolbox:share. No toolbox:manage fallback. Mirrors POST /toolbox/{id}/share's own check.
Whether the caller may transfer or delete this toolbox — ownership, full stop. No toolbox:manage fallback. Mirrors POST /toolbox/{id}/transfer and DELETE /toolbox/{id}.
Whether the caller may execute this toolbox's tools — the caller's grant-or-ownership level (§10). Present on every list and detail row for both templates and toolboxes — the two ACL-backed resource types with a use-gated action of their own.
The OAuth-connected apps that reach this toolbox, as a bounded rollup — present only on GET /toolbox/{id} for a stored toolbox, and only when the caller may see the ACL (the same gate access_summary uses). Never the full set: page GET /toolbox/{id}/connected-app for that.
2 properties
At most 5 apps, for a hover preview.
5 properties
Null when the OAuth client row is gone.
OAuth grant id (ogrt_…) — what DELETE /oauth/grant/{id} takes, scoped to the grant's own user.
3 properties
User id (usr_…).
Omitted along with email when the id no longer resolves to an org member.
toolbox: this toolbox is named explicitly on the grant. all_tools: an "All my tools" authorization by a user who can currently use this toolbox — computed live, not a stored fact.
toolboxall_tools
Present only for type: "connection" dynamic rows.
Present only for type: "connection" dynamic rows.
Which integrations this toolbox's tools come from, bounded — the console renders it as a stack of connector logos. Present on every GET /toolbox row: a stored row summarises its own entries, a connection:{id} row is that connection's one connector, and a global:{user_id} row summarises the connections it spans. Present on every command response too (POST /toolbox, GET /toolbox/{id}, PATCH /toolbox/{id}, POST /toolbox/{id}/transfer), so a client that merges one into its list does not show a just-created or just-edited toolbox as having no integrations. total: 0 means no connector-backed tools (empty, or synthetic-only), never "not computed".
2 properties
At most 5 connectors, ordered by entry count descending then slug ascending — the dominant integration leads and the order is stable across requests. Each entry arrives resolved: there is no follow-up GET /connector/{slug} to make, and a page of rows costs no per-row catalog lookup.
3 properties
The connector's square icon when it has one, else its wordmark logo, else null (the connector carries neither picture, or could not be resolved). Draw it in a square tile; render initials from name when it is null.
Catalog label. Falls back to the slug when the connector no longer resolves (deleted from the catalog), so a row always has something to draw.
Connector slug — connectors are keyed by slug, not by id.
Distinct connector slugs across the toolbox.
Null for dynamic rows, which are computed, never stored.
Omitted for dynamic rows, which have no stored entries of their own.
Toolbox id — a stored row (tbx_…), or a dynamic id (global:{user_id} / connection:{connection_id}).
Cheap per-page SQL aggregate over PROXY entries with connection_id IS NULL — drives the console's Status column ("Ready" / "N need connection"). Excludes synthetic entries, which pin no connection_id of their own by design and have no connection picker to fix. Omitted for dynamic rows (nothing to bind). Broken delegation (delegation_ok: false) only surfaces in detail, inside entries[].
Owner summary — resolved for a row the caller does not themselves own (a toolbox shared with them), so the UI always knows whose row it is looking at. Absent for a toolbox the caller owns.
3 properties
User id (usr_…) — same value as owner_user_id.
Creator (usr_…). For a dynamic row, the caller.
True for the dynamic global/per-connection toolboxes — never editable, shareable, transferable or deletable.
A bounded preview of the ACL (same cap as access_summary), present only on GET /toolbox/{id} when the caller may see the ACL. Never on a list row (GET /toolbox) — page GET /toolbox/{id}/share for the full, cursor-paginated grant list.
8 properties
User id (usr_…) or team id (team_…). Null for a grantee_type: "org" grant.
user and team grants target one grantee_id; org applies to every member of the organization and takes no id.
userteamorg
ACL entry id — the :aclId a DELETE .../share/{aclId} call takes.
Same ladder for every shareable resource, low to high. view — see it exists, read its metadata/config; cannot exercise it. use — view + exercise it, resource-specific: run tools through a connection; execute a toolbox's tools through its bound connections (this DELEGATES — the caller runs through each entry's pinning editor's own authority, not necessarily their own); stamp a new toolbox by copying a template's entries; create connections from a connector. edit — use + change its settings, entries and its own grants.
viewuseedit
The shared resource's id (a connector's slug, for that type).
connectiontoolboxtemplateconnector
Provenance only (stamped at creation, never a live link) — null for a from-scratch toolbox and for every dynamic row. May dangle after the template is deleted.
stored = a real ACL-backed row. global/connection = computed per request, always readonly: true.
storedglobalconnection
curl -X GET 'https://api.elaichi.ai/toolbox' \
-H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
-H 'Content-Type: application/json'const response = await fetch('https://api.elaichi.ai/toolbox', {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + process.env.ELAICHI_API_TOKEN,
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log(data);import os
import requests
url = "https://api.elaichi.ai/toolbox"
headers = {
"Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
"Content-Type": "application/json",
}
response = requests.get(url, headers=headers)
print(response.json())