---
title: "Redeem a link token"
method: POST
path: "/v1/assistant/link"
tags: ["Assistant"]
---

# Redeem a link token

`POST /v1/assistant/link`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/assistant/link`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Redeems a link token and creates the grant, binding the channel identity the token carries to the authenticated account. The identity is read from the token server-side — nothing in this request can point the link at a different one.
The token is the idempotency key: it is single-use, so a replay of this request fails rather than creating a second grant.

## Request body

- AssistantLinkRedeem
  - `token` string, required: The single-use token from the link the Assistant sent on the channel.

## Response `201`

Grant created. The Assistant answers that identity from the next message on.

- AssistantGrant: One channel identity authorized to operate one naturali account through the Assistant.
  - `id` string, required: Public grant ID (agr_ prefix).
  - `channel` 'discord' | 'whatsapp' | 'slack', required: The channel the linked identity lives on. The app is never a value here: a signed-in web session already *is* the identity, so it needs no grant.
  - `identifier` string, required: The channel identity as an address identifier (`discord:dm:<user_id>`, `whatsapp:dm:<phone>`), the one grammar channels use on the wire. One identity operates one account.
  - `display_name` string, nullable, required: The identity as its channel reported it, when it reported one — what a person recognizes instead of an opaque id. Null when the channel offers nothing better than the identifier.
  - `scopes` AssistantScope[], required: What the Assistant may do on the account. Deliberately coarse: the consent for a specific dangerous action is asked for when it happens, through the approval queue, rather than pre-granted here.
  - `status` 'active' | 'revoked', required: Whether the Assistant currently answers this identity.
  - `last_used_at` string, date-time, nullable, required: When the Assistant last acted under this grant; null if never.
  - `created_at` string, date-time, required

## Other responses

- `400`: The token is missing, unknown, expired, or already redeemed (`invalid_token` / `expired_token`).
- `401`: Missing or invalid credentials.
- `409`: The identity is already linked to an account (`identity_already_linked`). One channel identity operates one naturali account: re-pointing it means revoking the existing grant first, which is explicit and auditable rather than silent.

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
