Access requests

Approve or deny an access request

Approves or denies someone else’s access request. Requires member:manage. Only an interactive browser session can decide: an organization API token (Bearer elch_…) is refused with 403, however many permissions it holds, because approving an access request is a person’s decision. The session must also pass step-up reauthentication in the app; without a valid X-Step-Up-Token the call answers 428 step_up_required, and error.details names the action and resource to prove. A decision is recorded once and never re-opened, so the person recording it re-proves they are still the person holding the session. Refuses with 409 when the request has already left pending.

An admin may not decide their own request: 403, whichever decision they send and however many permissions they hold. Self-approval of a restriction request would be a self-grant, and the record would read as approved by a second person when it was not. The requesting admin keeps can_withdraw on that row instead.

Approving a restriction-reason request with resolution: "grant" (the default) lifts that restriction for the requester alone, either a whole connector or one tool on the connector a tool request names in connector_slug, by writing an access grant (who, what, approved by, when, and optionally an expiry). The requester’s own member-level rules are edited (and a block rule the approval empties is removed); where their role’s rules stand in the way, the grant is what lifts them. Role rules themselves are never edited or copied, so every other role rule, including one added later, keeps binding the requester. A tool on a connector blocked whole cannot be carved out and is not lifted. resolution: "policy" instead records the decision without writing a grant, for when the admin is fixing the underlying role or connector rule directly through the restriction APIs. If the requester has a personal rule that would need editing to open the target, an approval that lists remove_member_rules (the ids of the personal rules the caller confirms editing) must list exactly the conflicting ids, or the call answers 409 member_rule_conflict naming them in error.details.member_rule_conflicts. Omitting remove_member_rules skips that check and edits the personal rules without confirmation, for clients that predate the field. expires_at (ISO, in the future, at most about 10 years out; any UTC offset is accepted and stored as UTC) sets when a grant expires; omit it or send null for no expiry. will_lift_restriction on the admin view previews whether approving will change anything. For a fork blocked through its original (in the requester’s role or in their own rules), approving opens that fork for that person only and lifts_inherited_restriction says so: the original stays restricted for them, the grant is audited with lifted_inherited_restriction: true, and revoking it re-blocks the fork. Every other approval (a permission-reason request, or a tool request with no connector_slug) records a decision only; widening access there is a separate act through PATCH /member/{userId} or the restriction APIs.

post/access-request/{id}/resolve

Request

  • Base URL: https://api.elaichi.ai
  • URL: https://api.elaichi.ai/access-request/{id}/resolve
  • Auth: HTTP bearer

Path parameters

idstring required

Access request id (areq_…).

Headers

X-Organization-Idstring

Target organization (org_…). Required for cookie/session auth on org-scoped routes; optional for API tokens, which carry their organization, and rejected when it names a different one.

Request body

decision'approved' | 'denied' required
notestring
resolution'grant' | 'policy'

Only meaningful on an APPROVED, reason: "restriction" request. grant (default) writes an access grant; policy approves without writing one — the admin is changing the underlying rule directly instead.

expires_atstring date-time nullable

When the grant this approval writes auto-expires. null/omitted means no expiry. Ignored unless this approval actually writes a grant.

remove_member_rulesstring[]

The ids of the requester’s personal rules that this approval would edit, as the caller confirms them. Must equal the conflicting set exactly (an empty array asserts there are none), or the call answers 409 member_rule_conflict with the real ids in error.details. Omit the field to skip the check; the rules are then edited without confirmation.

Response

Resolved (admin view, can_resolve: false)

idstring

Access request id (areq_…).

requester_user_idstring

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

toolstring

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

resource_type'tool' | 'connector'
connector_slugstring nullable

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.

connector_labelstring

Display name of connector_slug (or, on a "connector" request, of tool itself), 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.

connector_logostring nullable

The connector’s logo for the slug connector_label names. null when the catalog has no logo for the slug; present under the same conditions as connector_label.

reason'permission' | 'restriction'

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

permissionstring nullable

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.

notestring nullable

Optional free text from the requester, ≤ 2000 characters.

status'pending' | 'approved' | 'denied' | 'withdrawn'
created_atstring date-time
resolved_atstring date-time nullable
resolved_by_user_idstring nullable

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

resolution_notestring nullable
restriction_liftedboolean nullable

What an approval did to the requester’s access, on every view: true when it wrote a grant, false when the request was approved and nothing was lifted (a resolution: "policy" approval, access that was already open, or a shape no grant can open), null when the question does not apply (not approved, a permission request, a tool request with no connector_slug). It never says why; the connector’s own restricted, restricted_by and restricted_inherited_from do, within what the caller may be told. An older approval may read false for access it did give, so read the connector’s restricted for the live answer. It is the outcome at resolve time: a later revoke, expiry or replacement does not change it.

updated_atstring date-time
can_resolveboolean

True while status is still pending and the row is not the caller’s own — the two preconditions resolve itself enforces (409 and 403 respectively).

can_withdrawboolean

True when the caller is the requester and status is still pending. Present on this shape as well as on the self view: an admin’s own request sits in their own queue, and withdrawing it is the one verb on that row that is theirs.

will_lift_restrictionboolean

On a pending restriction-reason request approving could act on (a connector request, or a tool request naming its connector_slug): whether approving it would actually lift anything. Absent on every other row.

connector_restricted_for_requesterboolean

On the same rows, for a TOOL request only: true when approving lifts nothing because the requester’s WHOLE connector is restricted — one tool cannot be carved out of a connector restricted whole, and an approval never opens the whole connector for a one-tool ask. Their connector access is what an admin would have to grant instead.

lifts_inherited_restrictionboolean

On the same rows, for a CONNECTOR or TOOL request: true when approving opens this FORK (or, for a tool request, that one tool on it) by lifting a block it inherits from the connector it was forked from — through the requester’s role OR through a rule written about them personally. A request for a fork is its own ask: approving it opens THAT FORK for THAT PERSON and nothing else; the original stays restricted for them, a rule naming the fork itself is not overridden, a connector approval never lifts their personal tool rules, and the exception is standing and tied to the fork (revoking the grant re-blocks it).

lifts_inherited_member_ruleboolean

With lifts_inherited_restriction: true when the block it lifts is a rule written about the requester personally rather than their role’s. Only then does the grant carry the marker that keeps lifting it while the grant is active — for the originals that blocked it when it was approved, so a block written later on a different ancestor is not bypassed. A lift of a role-layer block alone does not carry it, so a personal block written later on the original is not overridden by it.

inherited_from_member_ruleboolean

On the same rows, for a CONNECTOR request only: true when approving lifts nothing even though the connector is a fork blocked through its original in the requester’s own restrictions. This is now rare: most such forks are covered by lifts_inherited_restriction instead.

inherited_from_connector_labelstring nullable

With lifts_inherited_restriction or inherited_from_member_rule: the display name of the connector it was forked from, or null when it cannot be named to this caller (private to someone else, deleted, or the catalog did not answer). Never a slug or id.

Changes

No recorded changes to this operation across all 1 revision of this API.