> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clipstake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connecting accounts

> Mint an Instagram authorization link for your end user, read the result, and reconnect an account.

ClipStake reads Instagram through the account owner's own authorization. Your
end user approves once, and every project in your workspace can then track that
account's media.

## Mint a connect link

`POST /connect-links` returns a URL and nothing else. No row is written yet. A
connection is created by the callback, once Instagram returns a code, so links
that are generated and never clicked leave nothing behind.

```bash theme={null}
curl --request POST "https://api.clipstake.com/v1/connect-links" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "external_user_id": "your-user-123",
    "redirect_uri": "https://your-app.com/instagram/connected",
    "force_reauth": false
  }'
```

| Field              | Notes                                                                                                                                                              |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `external_user_id` | Optional. Your own id for the person. Opaque to ClipStake, scoped to your workspace. Omit it to use the account's Instagram handle.                                |
| `redirect_uri`     | Optional. An absolute URL on your site, where the user lands when the flow ends. Use HTTPS. Omit it and the user sees a ClipStake page that tells them the result. |
| `force_reauth`     | Optional. Show Instagram's account picker. Off by default.                                                                                                         |

The response carries an `id`, the `url`, and `expires_at`. A link is valid for
10 minutes. Mint it at the moment the user is about to click it, and keep the
`id` with that user's session.

## Read the result

When the flow ends, the user lands on your `redirect_uri` with three query
parameters: `status`, `username` and `connect_id`. Use them for display only.
Anyone can type them into a URL.

To act on the result, for example to sign the user in, check that `connect_id`
is the `id` you stored, then read the result with your API key:

```bash theme={null}
curl "https://api.clipstake.com/v1/connect-links/$CONNECT_ID" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY"
```

```json theme={null}
{
  "id": "3f1c9a52-6d1e-4b8a-9f0e-2c7d5a1b8e44.1789398000.Qm8vT2xk3NcR7pLwZ0yH4sFbJ6aUeD1i",
  "status": "external_user_id_mismatch",
  "message": "This Instagram account is already connected under a different external_user_id.",
  "account": {
    "username": "creator_handle",
    "external_user_id": "your-user-456"
  },
  "expires_at": "2026-09-14T11:00:00.000Z"
}
```

### `status` from `GET /connect-links/{id}`

| `status`                    | Meaning                                                                                                                                                                                  |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`                   | The user has not finished yet, or cancelled on Instagram.                                                                                                                                |
| `expired`                   | The link lapsed without a result.                                                                                                                                                        |
| `connected`                 | The account is connected. `account` names the account and its `external_user_id`.                                                                                                        |
| `external_user_id_mismatch` | The account is already connected in this workspace under a different `external_user_id`. Nothing was changed. `account` names the existing connection, and `message` says what happened. |
| `already_linked`            | Another connect of the same account finished at the same time and won. `account` is `null`. List your accounts to see which user holds it.                                               |
| `needs_professional`        | The account is not a Business or Creator account. Nothing was connected.                                                                                                                 |

`message` is set only on `external_user_id_mismatch`. It is `null` for every
other status. Branch on `status`, not on `message`.

### `status` on the redirect

The redirect's `status` query parameter carries the same values, with three
differences:

| `status`          | Meaning                                                                                                                                          |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `access_denied`   | The user cancelled on Instagram. The API read reports `pending` until the link expires.                                                          |
| `exchange_failed` | ClipStake could not finish the exchange with Instagram. The API read reports `pending` until the link expires. Send the user through a new link. |
| `already_linked`  | Also sent when the browser opens the same finished callback a second time. The API read still reports the first result.                          |

`username` is sent only when the status is `connected`, or on a repeat of a
connected callback. It is never sent for `external_user_id_mismatch`.

## One connection per account in each workspace

A connection is unique for each workspace and Instagram account. The rules that
follow from that:

* **Same workspace, same `external_user_id`.** This is a reconnect. The
  existing connection gets the new authorization and keeps its history.
* **Same workspace, different `external_user_id`.** This is a mismatch. The
  connect returns `external_user_id_mismatch` and changes nothing, so one of
  your users can never take another user's tracked media.
* **Different workspace.** The connect succeeds. Each workspace gets its own
  connection and its own authorization.

One `external_user_id` can hold more than one Instagram account.

### Resolve a mismatch

A mismatch means the account already belongs to another of your users.

* If it is the same person, sign them in as the user in
  `account.external_user_id`.
* If it is a different person, handle it in your own support process.

ClipStake does not move a connection from one `external_user_id` to another.

## Use the handle as your user id

If your app has no user id of its own, omit `external_user_id`. The account is
connected under its Instagram handle, and `account.external_user_id` returns
it. When the same person connects again, the connection is refreshed, not
duplicated.

## When to set `force_reauth`

Instagram authorizes whichever account the browser is already signed in to. For
someone who granted the scopes before, it does that without stopping to ask.

On a first connect the flag changes nothing. On a second one it decides whether
your user picks the account or Instagram picks for them.

<Warning>
  If your flow lets one person connect more than one handle, pass
  `force_reauth: true`. Without it the consent screen can flash past and
  reconnect the first account, with no error to explain it.
</Warning>

## The connection belongs to the workspace

A connect link is minted with a workspace key, and the connection it creates is
reachable from every project in that workspace. Projects separate tracked
media, not people.

## List connected accounts

```bash theme={null}
curl --get "https://api.clipstake.com/v1/accounts" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY" \
  --data-urlencode "external_user_id=your-user-123"
```

Every connected account is listed, including accounts with no tracked media
yet.

### `connection_status`

| Value          | Meaning                                                                    |
| -------------- | -------------------------------------------------------------------------- |
| `active`       | The authorization works.                                                   |
| `needs_reauth` | The authorization expired or Instagram rejected it. Reconnect the account. |
| `revoked`      | The user removed access. Reconnect the account.                            |

While a connection is not `active`, `POST /submissions` and the audience and
insights reads for that account return `409 conflict`.

### `tracking_status`

Tracking follows submissions. You never start or stop an account directly.

| Value     | Meaning                                                                     |
| --------- | --------------------------------------------------------------------------- |
| `active`  | The account has at least one trackable media, so it is being read.          |
| `parked`  | Reading is on hold. The usual cause is a connection that needs a reconnect. |
| `stopped` | No eligible tracked media remains.                                          |

Accepting the first eligible media enrolls the account. Pausing or stopping the
last one ends collection. There is no account start, pause, or resume endpoint.

## Keeping a connection active

ClipStake keeps each connection's authorization current automatically. You do
not need to do anything while the connection is `active`.

An authorization that expires, or that Instagram rejects, moves the connection
to `needs_reauth`. Only a reconnect repairs it.

## Reconnecting

To reconnect an account, mint a new connect link with the **same**
`external_user_id` as the existing connection. Pass `force_reauth: true` so the
user picks the right account on Instagram.

```bash theme={null}
curl --request POST "https://api.clipstake.com/v1/connect-links" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "external_user_id": "your-user-123",
    "redirect_uri": "https://your-app.com/instagram/connected",
    "force_reauth": true
  }'
```

A reconnect refreshes the existing connection, sets it back to `active`, and
keeps its history. A different `external_user_id` returns
`external_user_id_mismatch` instead.

### What happens to tracked media

Media that was `parked` because the connection needed a reconnect goes back on
the schedule automatically. Each media reads at its next scheduled slot. This
applies in every non-archived project of the workspace.

Two limits apply:

* Automatic resume respects your submission capacity. Media that does not fit
  stays `parked`. Resume it after you raise the plan.
* Media that was `parked` because its reads kept failing is not resumed by a
  reconnect. Resume it yourself. See
  [Tracking media](/api-reference/tracking-media#parked-media).

Media you paused yourself stays paused.
