> ## 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.

# Tracking media

> Submit Instagram URLs, read per-URL results, and control what stays on the schedule.

Tracked media is one piece of Instagram content that ClipStake reads on a
schedule. Every reading is kept, so the value of the product is the series rather than any
single number.

## Submit URLs

`POST /submissions` takes up to 100 canonical Instagram URLs for one connected
account.

```bash theme={null}
curl --request POST "https://api.clipstake.com/v1/submissions" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "project_id": "3f1c9f0a-2f2c-4c0f-9a7d-8a1c0c4e5b21",
    "username": "creator_handle",
    "urls": [
      "https://www.instagram.com/reel/DAbc123XyZ/",
      "https://www.instagram.com/p/DBcd456UvW/"
    ]
  }'
```

Accepted URL shapes are `https://instagram.com` or `https://www.instagram.com`
with a `/reel/`, `/reels/`, `/p/`, or `/tv/` path. A bare shortcode is not a
URL and is rejected.

### One result per URL

The request succeeds even when some URLs do not resolve. Read `status` on each
item.

| `status`          | Meaning                                             |
| ----------------- | --------------------------------------------------- |
| `created`         | Now tracked. `submission` carries the row.          |
| `already_tracked` | This project already tracks it. Safe to send again. |
| `not_found`       | Not found in this account's recent media.           |

Submitting is idempotent per project and shortcode, so retrying a whole batch
never creates duplicates.

### Why tracked media can come back `not_found`

ClipStake finds each URL by looking for the post among the connected
account's recent media, up to its 500 most recent posts.

Media can therefore be reported `not_found` when it is older than that window,
when it belongs to a different account than the `username` you sent, or when it
was deleted or made private.

<Note>
  Concurrent requests for the same account share one lookup, so the
  account's rate limit is spent once. If a lookup is already running and does
  not finish in time, the request returns `409 conflict` with a `Retry-After`
  header.
  Retry once and the warmed result is used.
</Note>

### Errors that fail the whole request

| Status | `code`           | Cause                                                                                                                                                                                                                     |
| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 402    | `quota_exceeded` | The new media would take the workspace past its submission or account limit. The check covers the whole batch, so no media from it is created.                                                                            |
| 404    | `not_found`      | No connected account with that `username`, or no such `project_id`, in this workspace.                                                                                                                                    |
| 409    | `conflict`       | The connection for `username` is `needs_reauth` or `revoked`, Instagram rejected the account's authorization during the lookup, or a lookup for this account is already running. Reconnect the account for the first two. |
| 429    | `rate_limited`   | Instagram is rate limiting this account. Try again later.                                                                                                                                                                 |

## Status lifecycle

| `status`      | Meaning                                                                                |
| ------------- | -------------------------------------------------------------------------------------- |
| `active`      | On the schedule. `next_read_at` is the earliest allowed read.                          |
| `paused`      | You paused it. History is kept and nothing is read.                                    |
| `unavailable` | The media no longer resolves. `unavailable_reason` says why. Terminal.                 |
| `parked`      | Reading is on hold for a reason outside this media. See [Parked media](#parked-media). |
| `stopped`     | Archived with its project, or explicitly stopped. Resume is explicit.                  |

`next_read_at` is the earliest moment ClipStake is allowed to read the media.
It is not a promise that a read happens at that instant. It is `null` once the
media is no longer read at all.

<Warning>
  `unavailable` media cannot be resumed. It is a verdict about the media, not a
  state you moved it into, so resuming it would fail again on every cycle
  forever. The API answers `409 conflict`.
</Warning>

A billing hold does not change any status. When a renewal fails and the grace
period ends, reads stop but every media keeps its status. Paying the invoice
starts reads again, with nothing to resume. See
[Plans and limits](/api-reference/plans-and-limits#failed-payments).

## Parked media

A media is `parked` when ClipStake stops reading it for a reason that is not
about the media itself. There are two causes:

| `parked_reason` | What happened                                                                                                  | How it comes back                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `token_invalid` | The account's authorization expired or Instagram rejected it. Every active media on that connection is parked. | Automatically, when you [reconnect the account](/api-reference/connecting-accounts#reconnecting). |
| `read_failed`   | Five reads of this media failed in a row for a reason that was not the media's fault.                          | Only when you resume it.                                                                          |

`parked_reason` is set only while `status` is `parked`, and is `null` in every
other status. `GET /accounts` carries the same field for an account whose
`tracking_status` is `parked`.

Resume a parked media with `POST /submissions/{shortcode}/resume`. Resuming
clears the hold and the failure count, and the media is due at once.

<Warning>
  Resuming a parked media while its connection still needs a reconnect returns
  `409 conflict`. Reconnect the account first. The reconnect then resumes that
  connection's media for you.
</Warning>

An automatic resume after a reconnect respects your submission capacity. Media
that does not fit stays `parked` until you raise the plan and resume it.

## Pause and resume

Pause one media, keeping everything already collected:

```bash theme={null}
curl --request POST "https://api.clipstake.com/v1/submissions/DAbc123XyZ/pause" \
  --header "Authorization: Bearer $CLIPSTAKE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"project_id": "3f1c9f0a-2f2c-4c0f-9a7d-8a1c0c4e5b21"}'
```

Or act on a whole project with `POST /submissions/pause` and
`POST /submissions/resume`. Both take `project_id` and return how many rows
they affected. A resume also returns `left_paused`: rows it could not admit
because the workspace is at its submission limit.

The bulk resume acts on `paused` and `stopped` media only. It does not resume
`parked` media. Resume those one at a time, or reconnect the account.

Resumed media is due at once. Views are a running total, so the first reading
after a pause picks up the current figure. The gap in the series is real and
stays visible. It is never backfilled.

<Note>
  Reads happen on a fixed UTC schedule. A media read every 4 hours is read at
  00:00, 04:00, 08:00 and so on, UTC. A bulk resume puts each row on the next
  slot of that schedule, not at the moment of the resume.
</Note>

The literal shortcodes `pause` and `resume` are reserved, so the project-wide
routes can never be read as a shortcode.

## Reading the data

`GET /submissions` lists tracked media in a project. It filters on `status`,
`external_user_id`, and `username`, and pages with `limit` and `cursor`.

`GET /submissions/{shortcode}/stats` returns the time series: one point per
reading, with `captured_at`, the metrics, and watch time where Instagram
reports it.

### `null` never means zero

Every metric is nullable. `null` means Instagram did not report the metric for
that media. A `0` is a real reading.

Do not coerce one into the other. An account that is not eligible for a metric
would become indistinguishable from an account measuring zero.

## Deleting

There is no delete endpoint, and that is deliberate. The old delete only
changed a status, so it was an irreversible pause with no other effect. Pause
stops reads and keeps history, and the `status` filter on `GET /submissions`
covers list hygiene.
