Skip to content
POST /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

connector_slugstring

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.

notestring
permissionstring

Required when reason is "permission"; forbidden (400) when reason is "restriction".

reasonstring
Possible values:
permissionrestriction
resource_typestring
Possible values:
toolconnector
toolstring

The tool name the caller was blocked from calling, or a connector slug when resource_type is "connector".

Response Body

can_withdrawboolean

True when the caller is the requester and status is still pending.

connector_slugstring,null

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.

created_atstring · date-time
idstring

Access request id (areq_…).

notestring,null

Optional free text from the requester, ≤ 2000 characters.

permissionstring,null

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.

reasonstring

What kind of refusal this request is asking to be reconsidered.

Possible values:
permissionrestriction
requester_user_idstring

User id (usr_…) of whoever filed the request.

resolution_notestring,null
resolved_atstring,null · date-time
resolved_by_user_idstring,null

User id (usr_…) of the admin who resolved it.

resource_typestring
Possible values:
toolconnector
statusstring
Possible values:
pendingapproveddeniedwithdrawn
toolstring

What was asked for: a tool name, or a connector slug when resource_type is "connector".

updated_atstring · date-time
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())