File an access request
/access-request
Any authenticated member — no permission required. Requiring a permission to ask for a permission would be a deadlock: the member who most needs this route holds the fewest permissions. Membership (`orgContext()`) is the only gate, and there is no `requester_user_id` in the body — a request is always filed as the caller. `permission` is required and must be a known permission name when `reason` is `"permission"`, and forbidden (a `400`) when `reason` is `"restriction"` — the same disclosure rule a restriction refusal already enforces on the refusal itself: this resource must not become a second way to learn which rule blocked someone. `reason` is never `"scope"` — a scope refusal has no admin-side fix and never sets `can_request_access`. **Filing never grants anything.** It creates a record for an admin to decide; approving a `restriction`-reason request is what can lift the restriction (see `resolveAccessRequest`). **De-duplicated.** A second request for the same `tool` while an earlier one from the same requester is still `pending` does not create a new row — it returns the existing one (updating `note` when a different one was sent) with `200`. A genuinely new row is `201`. **A tool request that could never be granted is refused.** A `restriction`-reason `"tool"` request naming a `connector_slug` whose WHOLE connector is restricted for the caller is `409 tool_request_connector_blocked`: approving it would lift nothing (one tool cannot be carved out of a connector restricted whole, and an approval never opens the whole connector for a one-tool ask). The message names the connector and says to ask for it instead; `error.details` carries `connector_slug`, `connector_label` and `pending_request` (the caller’s own open connector request, or null). Every tool row that can draw an ask carries `restricted_scope` (`"connector" | "tool" | null`), so a client files the connector request up front.
Request Body
Only on a "tool" request (400 on a connector request): the connector the tool lives on. Lets an approval lift the restriction on exactly that tool; without it approving records a decision only.
Required when reason is "permission"; forbidden (400) when reason is "restriction".
permissionrestriction
toolconnector
The tool name the caller was blocked from calling, or a connector slug when resource_type is "connector".
Response Body
True when the caller is the requester and status is still pending.
Display name of connector_slug (or, on a "connector" request, of tool itself) — resolved from the catalog in the same batch as tool_label, falling back to the slug. Present on every view: the requester reads it in the self view exactly as an admin does in the queue, so it lives here rather than only on the admin shape.
The connector’s mark for the same slug connector_label names — the icon-first, logo-second image the Connectors page and every other connector picture in the console already use. null when the catalog has no mark for the slug; present under the same conditions as connector_label.
On a "tool" request, the connector the tool lives on when the request named one — what makes approving it able to lift the restriction on that tool. Always null on a "connector" request.
Access request id (areq_…).
Optional free text from the requester, ≤ 2000 characters.
Present only when reason is "permission" — never populated for a "restriction"-reason request, on either write or read. That is the disclosure rule: a restriction refusal never names the rule that blocked the caller, so this field must not become a second channel for the same fact.
What kind of refusal this request is asking to be reconsidered.
permissionrestriction
User id (usr_…) of whoever filed the request.
User id (usr_…) of the admin who resolved it.
toolconnector
pendingapproveddeniedwithdrawn
What was asked for: a tool name, or a connector slug when resource_type is "connector".
curl -X POST 'https://api.elaichi.ai/access-request' \
-H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"tool":"your_tool","resource_type":"tool","connector_slug":"your_connector_slug","reason":"permission","permission":"your_permission","note":"your_note"}'const body = {
"tool": "your_tool",
"resource_type": "tool",
"connector_slug": "your_connector_slug",
"reason": "permission",
"permission": "your_permission",
"note": "your_note"
};
const response = await fetch('https://api.elaichi.ai/access-request', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ELAICHI_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
console.log(data);import os
import requests
url = "https://api.elaichi.ai/access-request"
headers = {
"Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
"Content-Type": "application/json",
}
payload = {
"tool": "your_tool",
"resource_type": "tool",
"connector_slug": "your_connector_slug",
"reason": "permission",
"permission": "your_permission",
"note": "your_note"
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())