# Unfurl a link

`POST /chat.unfurl`

## Description

Attach app-provided preview cards to links in an existing text message.
Every map key must be an exact URL currently present in the message and
must match one of the app's registered unfurl domains. Validation is
atomic: if any entry is invalid, no previews are changed.

App previews replace Roam-generated previews for the same exact URL while
preserving unrelated previews. The server does not fetch any URL supplied
in this request.

**Access:** Organization only (API Key or OAuth). Register unfurl domains on
the API client first — see [Unfurling links](/docs/guides/unfurling-links).
Personal Access Tokens cannot register domains or call this endpoint.

**Required scope:** `links:write`

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## Request Body

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `chatId` | string | Yes |  |
| `messageTimestamp` | integer | Yes | Timestamp of a top-level or threaded message in Unix microseconds. |
| `unfurls` | object | Yes | Preview content keyed by the exact URL from the message. |

### Example Request

```json
{
  "chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
  "messageTimestamp": 1748906400000000,
  "unfurls": {
    "https://status.example.com/incidents/123": {
      "title": "Incident 123",
      "description": "Investigating elevated errors",
      "siteName": "PagerDuty",
      "favicon": "https://status.example.com/favicon.png",
      "image": {
        "url": "https://status.example.com/incident.png",
        "type": "image/png",
        "width": 1200,
        "height": 630,
        "alt": "Incident status"
      }
    }
  }
}
```

## Responses

### 200 - Preview cards applied successfully.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `ok` | "true" | Yes |  |


#### Example Response

```json
{
  "ok": true
}
```

### 400 - Invalid parameters or `cannot_unfurl_url` when a URL is absent from the message or outside the app's registered domains.

### 401 - Presented invalid authentication credentials.

### 403 - Missing `links:write` scope or no access to the chat.

### 404 - The chat or message was not found.

### 405 - An unsupported method was requested.

### 409 - The message changed twice while previews were being applied; retry the request.

### 500 - An internal error occurred.

---

*Machine-readable API documentation.*
*Full documentation: https://developer.ro.am/docs/api/chat-unfurl*
