---
title: "Download the file of a snapshot"
method: GET
path: "/snapshots/download"
tags: ["Snapshots"]
---

# Download the file of a snapshot

`GET /snapshots/download`

## Request

- Base URL: `https://api.reportingninja.com/v1`
- URL: `https://api.reportingninja.com/v1/snapshots/download`
- Auth: one of:
  - HTTP bearer
  - none

Returns the file of a snapshot. It is one of the TWO endpoints of this API that are a GET, whose success is not a JSON envelope and that have no MCP tool of their own; the other one is GET /v1/language-files/download.

TWO WAYS IN, AND ONE OF THEM IS ENOUGH (a request that carries both is served by the token). `token` is the signed link /v1/snapshots/list publishes as `download_url`: it carries the account and the snapshot inside it, needs NO Authorization header -- which is what lets an assistant hand the link to a person -- and expires 900 seconds after it was minted. `snapshot_key` is the form a script uses, with the Bearer of the account; it is charged against the read rate limit like any other call.

The response is the file itself, `application/pdf` or `application/vnd.ms-excel` according to the name it was stored under, with a Content-Disposition attachment whose filename is that name reduced to ASCII. The signed form has a budget of its own, much narrower than the read rate limit: a link is good for a handful of downloads a minute, which is what handing a file to somebody takes and not what mirroring an account takes. Everything that is not a file comes back in the envelope of the rest of the API: 400 when neither `token` nor `snapshot_key` was sent, 401 with no credentials, 404 when the link expired, the snapshot is gone, it belongs to another account or its execution filed no file, and 429 over the rate limit. A POST to this path is ENDPOINT_NOT_FOUND, and a HEAD is 405.

## Query parameters

- `token` string
- `snapshot_key` string

## Response `200`

The file.

## Other responses

- `400`: The request was refused before anything was written. INVALID_REQUEST covers the framework refusals of every endpoint (the Content-Type is not application/json, the body could not be read, the body is not valid JSON). The report endpoints add their own: VALIDATION_FAILED with details.problems, UNKNOWN_PARAM with the path of the field the endpoint does not read, and the single-field refusals MISSING_REQUIRED_FIELD, INVALID_VALUE and APPEND_NOT_ALLOWED. The description of each operation lists the ones it emits. Everything else a body can get wrong arrives as an entry of details.problems rather than as the code of the envelope.
- `401`: Invalid or missing Bearer token.
- `404`: A key of the request does not name anything this account owns. NOT_FOUND covers reports, sections, widgets, schedules, portal views, brand templates, email templates and language files; TEMPLATE_NOT_FOUND covers a catalog slug, version or section position that stopped being published between the validation and the write. An entity of another account is reported the same way as one that does not exist.
- `429`: A budget was spent. RATE_LIMITED is the per-key read budget (300/minute, 5000/day) or the per-account write budget (60/minute, 1000/day). EMAIL_QUOTA_REACHED is the separate budget of 50 calls per account per day that send email, and its Retry-After points at the next UTC midnight. The Retry-After header gives the seconds until the more restrictive window resets. The X-RateLimit-* headers report the read budget, except on the 429 of a write, where they report the write budget.
- `500`: Internal server error.

---

[API](https://skmtc.dev/reportingninja/apis/reporting-ninja-api.md) · [All operations](https://skmtc.dev/reportingninja/apis/reporting-ninja-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reportingninja/apis/reporting-ninja-api/revisions/13876c71be34?raw)
