Skip to content

Request Access Through a Dead Share Link

Asks a chat's owner to let the caller back in after the share link they were given stopped working. Call it with the slug of a link that answered Get Shared Thread with 422 share_revoked or share_expired and details.can_request_access: true.

The first call files one request and notifies the owner with a thread.access_requested notification. A repeat call while that request is still pending returns 200 with the same id and notifies nobody again, so a double-press is harmless.

The owner answers from List Access Requests:

  • Grant creates a new explicit share link naming only the caller. The caller receives the usual thread.shared notification carrying the new link.
  • Dismiss closes the request. The caller is not notified, and may not ask again for 24 hours.

Sharing the chat with the caller again through Create Share Link also answers the request as granted.

The request's current state is returned as details.access_request whenever the dead link is opened again, so the dead-link screen can show "Request sent" without a separate lookup.

POSThttps://api.aitronos.com/v1/shared-threads/{slug}/access-requests

Authentication

Requires user authentication (Bearer token). API-key authentication is rejected with 403. A share link names people, and an API key is never a person.

Path Parameters

slug string required

The slug of the share link that stopped working.

Body Parameters

The body is optional. Send no body, or send {}.

message string | null optional

A note to the owner, up to 1000 characters. A longer note is refused with 422; it is never shortened. The owner's notification quotes the first 200 characters.

Returns

201 Created when this call filed the request. 200 OK when a pending request already existed. Both return the same shape:

  • id string: the access request ID (tsar_…).
  • status string: pending, granted or dismissed. A new request is always pending.
  • created_at string: when the request was filed (ISO 8601 UTC).
  • decided_at string | null: when it was granted or dismissed. null while pending.
  • retry_after integer | null: seconds until the caller may ask again. Set only on a dismissed request whose 24-hour cooldown is still running.
  • created boolean: true with 201, false with 200.

The owner's notification

The chat's owner receives one notification per request:

FieldValue
event_typethread.access_requested
title{requester} asked for access to “{chat title}”
bodyTheir share link no longer works. Grant access to send {requester} a new link. If the requester left a note, They wrote: “…” is appended.
linknull. Open the chat from payload.thread_id.
actor_user_idThe requester.

payload:

  • request_id string: pass it to Grant or Dismiss.
  • thread_id string: the chat.
  • thread_title string | null
  • link_id string | null: the dead link the requester asked through.
  • access_level string | null: that link's level (view, fork or live). A grant reuses it by default.
  • requester_user_id string
  • requester_name string | null: a display name, never an email address.
  • message string | null: the requester's note, at most 200 characters.

The notification is resolved (moved to responded) as soon as the request is granted or dismissed by anyone with edit access, or answered by a re-share, so it never lingers as an open action.

Code Examples

cURL

curl -X POST https://api.aitronos.com/v1/shared-threads/abc123slug/access-requests \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "I was reviewing this for the board meeting."}'

Python (requests)

import requests

response = requests.post(
    "https://api.aitronos.com/v1/shared-threads/abc123slug/access-requests",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json={"message": "I was reviewing this for the board meeting."},
)
print(response.status_code, response.json())  # 201 on the first press, 200 after

JavaScript

const response = await fetch(
  "https://api.aitronos.com/v1/shared-threads/abc123slug/access-requests",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_TOKEN",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ message: "I was reviewing this for the board meeting." }),
  },
);
const request = await response.json();

Response Examples

Filed (201)

{
  "id": "tsar_5b1f0c2d9e8a4b7c9d6e5f4a3b2c1d0e",
  "status": "pending",
  "created_at": "2026-09-30T17:48:02Z",
  "decided_at": null,
  "retry_after": null,
  "created": true
}

Already pending (200)

The same request, with created: false. Nobody is notified again.

{
  "id": "tsar_5b1f0c2d9e8a4b7c9d6e5f4a3b2c1d0e",
  "status": "pending",
  "created_at": "2026-09-30T17:48:02Z",
  "decided_at": null,
  "retry_after": null,
  "created": false
}

Error: not found (404)

This is the uniform body the share resolver uses. It is returned for every caller the link was not given to: an unknown slug, a member who was never granted an explicit link, and a former member of the organization. It is also returned for a chat that was deleted. It never reveals whether the slug exists.

{
  "success": false,
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested resource could not be found.",
    "system_message": "Share link not found",
    "type": "client_error",
    "status": 404,
    "details": {},
    "trace_id": "...",
    "timestamp": "..."
  }
}

Error: asking would change nothing (409)

THREAD_ACCESS_REQUEST_NOT_NEEDED. details.reason is one of:

  • link_active: the link works again.
  • caller_is_owner: the caller owns the chat.
  • has_other_access: another share link or a space still opens the chat for the caller.

details.thread_id is included with caller_is_owner and has_other_access, because the caller can already open the chat.

{
  "success": false,
  "error": {
    "code": "THREAD_ACCESS_REQUEST_NOT_NEEDED",
    "message": "You can already open this chat, so there is nothing to ask for.",
    "system_message": "The caller can already open this chat",
    "type": "client_error",
    "status": 409,
    "details": { "reason": "has_other_access", "thread_id": "thread_8f3c" },
    "trace_id": "...",
    "timestamp": "..."
  }
}

Error: the owner can no longer be asked (409)

THREAD_ACCESS_REQUEST_OWNER_UNAVAILABLE. The owner's account is deactivated, or the owner left the organization.

{
  "success": false,
  "error": {
    "code": "THREAD_ACCESS_REQUEST_OWNER_UNAVAILABLE",
    "message": "The owner of this chat can no longer be asked for access.",
    "system_message": "The chat's owner can no longer be asked for access",
    "type": "client_error",
    "status": 409,
    "details": { "reason": "owner_inactive" },
    "trace_id": "...",
    "timestamp": "..."
  }
}

Error: dismissed recently (429)

THREAD_ACCESS_REQUEST_COOLDOWN. The caller's last request for this chat was dismissed less than 24 hours ago. details.retry_after is in seconds and is rounded up.

{
  "success": false,
  "error": {
    "code": "THREAD_ACCESS_REQUEST_COOLDOWN",
    "message": "Your last request for this chat was dismissed. You can ask again later.",
    "system_message": "A dismissed access request may not be re-filed yet",
    "type": "client_error",
    "status": 429,
    "details": { "retry_after": 86217 },
    "trace_id": "...",
    "timestamp": "..."
  }
}

Error: rate limited (429)

RATE_LIMIT_EXCEEDED. The caller made more than 10 requests to this route within 60 seconds, across all slugs.

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "...",
    "system_message": "Rate limit exceeded",
    "type": "client_error",
    "status": 429,
    "details": { "limit": 10, "window_seconds": 60, "retry_after": 41 },
    "trace_id": "...",
    "timestamp": "..."
  }
}