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
explicitshare link naming only the caller. The caller receives the usualthread.sharednotification 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.
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.
slug string required
The slug of the share link that stopped working.
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.
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,grantedordismissed. A new request is alwayspending. - created_at
string: when the request was filed (ISO 8601 UTC). - decided_at
string | null: when it was granted or dismissed.nullwhile pending. - retry_after
integer | null: seconds until the caller may ask again. Set only on adismissedrequest whose 24-hour cooldown is still running. - created
boolean:truewith201,falsewith200.
The chat's owner receives one notification per request:
| Field | Value |
|---|---|
event_type | thread.access_requested |
title | {requester} asked for access to “{chat title}” |
body | Their share link no longer works. Grant access to send {requester} a new link. If the requester left a note, They wrote: “…” is appended. |
link | null. Open the chat from payload.thread_id. |
actor_user_id | The 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,forkorlive). 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.
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."}'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 afterconst 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();{
"id": "tsar_5b1f0c2d9e8a4b7c9d6e5f4a3b2c1d0e",
"status": "pending",
"created_at": "2026-09-30T17:48:02Z",
"decided_at": null,
"retry_after": null,
"created": true
}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
}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": "..."
}
}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": "..."
}
}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": "..."
}
}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": "..."
}
}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": "..."
}
}- Get Shared Thread: the dead-link reasons and
details.access_request - List Access Requests: the owner's view
- Grant Access Request
- Dismiss Access Request