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

> ## Agent Instructions
> Shippo is a multi-carrier shipping API. For agent integrations that execute shipping operations (rates, labels, tracking, address validation, customs), connect the hosted Shippo MCP server at https://mcp.shippo.com (per-user OAuth; setup at /guides/mcp-server). To search and read this documentation from an agent, a docs search MCP is available at https://docs.goshippo.com/mcp. Shipping workflow knowledge (agent skills and a knowledge pack) is published at https://github.com/goshippo/ai. For REST integrations start at /guides/api-quickstart; test mode uses shippo_test_ API keys.

# Core Concepts

> Report types, runs, lifecycle, idempotency, and errors in the Reporting API.

The Reporting API exports your Shippo account data as downloadable CSV or Parquet files. You create a run, poll until it finishes, then download the file. See the [Quickstart](/reporting-api/quickstart) for a runnable walkthrough, or [Authentication](/guides/authentication) for token setup.

## Report types

A **report type** is a versioned export definition, such as `invoice.v1`. Fetch the catalog:

```shell theme={null}
curl https://api.goshippo.com/v2/reporting/reports \
  -H "Authorization: ShippoToken <API_TOKEN>"
```

The response is `{ "items": [...] }`. Each item has:

| Field               | What it describes                                   |
| ------------------- | --------------------------------------------------- |
| `available_columns` | Columns you can request. Pass `name` to select one. |
| `data_availability` | The time window for which data exists.              |

To fetch a single type, call `GET /v2/reporting/reports/{report_type}`. An unknown key returns `404`.

While the window is still being determined, `data_availability` is `{ "status": "pending" }`. Once it's ready, it becomes `{ "status": "ready", "start": ..., "end": ..., "updated": ... }` in UTC epoch seconds. The `updated` field records when those bounds were last checked. If you request a run outside the ready range, you get a `404` (`data-unavailable`).

### Columns

Pass column `name` values in your run request to pick the columns you want. Omit `columns` to get the default set. Each report type allows its own set of columns, listed as `available_columns`.

## Runs

Create a run with `POST /v2/reporting/runs`:

```shell theme={null}
curl https://api.goshippo.com/v2/reporting/runs \
  -H "Authorization: ShippoToken <API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "report_type": "invoice.v1",
    "interval": { "start": 1704067200, "end": 1706745600 },
    "columns": ["invoice_number", "invoice_total_amount"],
    "output_format": "csv_gzip"
  }'
```

| Field             | Required | Notes                                                                                                                                                           |
| ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `report_type`     | yes      | A versioned key from the catalog.                                                                                                                               |
| `interval`        | yes      | `{ start, end }` in UTC epoch seconds. `end` must be greater than `start`, or you get `422`. The span can't exceed 92 days, or you get `400 invalid-parameter`. |
| `columns`         | no       | A subset of `available_columns` names. Omit for the default set.                                                                                                |
| `output_format`   | no       | `csv`, `csv_gzip` (default), or `parquet`.                                                                                                                      |
| `idempotency_key` | no       | Your own string, 1 to 128 characters. See [Idempotency](#idempotency).                                                                                          |

All timestamps are integer UTC epoch seconds, not ISO strings or milliseconds.

There are no filters. You pick the report type, the date range, and the columns, and a run exports every row in that window.

<Warning>
  Unknown fields in the request body are rejected with `422`.
</Warning>

To list recent runs, call `GET /v2/reporting/runs`. You can pass `limit` (default 50, range 1 to 500). List responses never include a download URL, so fetch a single run to get one.

## Lifecycle

A run moves through these states:
Reporting API uses uppercase run statuses to match Shippo's existing reports API lifecycle vocabulary.

```text theme={null}
QUEUED  ->  PROCESSING  ->  SUCCEEDED
                           \->  FAILED
```

Poll `GET /v2/reporting/runs/{run_id}` until `status` is `SUCCEEDED` or `FAILED`. A run that exceeds the processing limit ends as `FAILED` with `run_timeout`.

<Warning>
  A run with `status: FAILED` returns HTTP 200 with `status: FAILED`, not an HTTP error. Check `status` (and `error.code`) before treating a run as successful.
</Warning>

A run with `status: SUCCEEDED` looks like this:

```json theme={null}
{
  "id": "8f2b1c0e-4a6d-4f8a-9b2e-3c7d1e5f9a04",
  "status": "SUCCEEDED",
  "completed": 1706745912,
  "result": {
    "row_count": 1842,
    "size_bytes": 50431,
    "download": {
      "status": "available",
      "url": "https://<download-host>/<opaque-path>?<signature>",
      "expires": 1706832312
    }
  }
}
```

### Download

The `result.download` field is either `{ "status": "available", "url": ..., "expires": ... }` or `{ "status": "unavailable" }`. When it's unavailable, GET the run again to get a fresh URL.

```shell theme={null}
# A Shippo Authorization header is not required for this request.
curl -L -o report.csv.gz "$DOWNLOAD_URL"
```

<Info>The URL is time-limited. If it expires, fetch the run again for a new one.</Info>

## Idempotency

You can pass an optional `idempotency_key`, a string of your choosing, 1 to 128 characters, to make a create call safe to retry.

| Request                                  | Result                                                                                                                          |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| No key, or a key you haven't used before | `201 Created` with a new run (`status: QUEUED`).                                                                                |
| Same key, same request                   | `200 OK` returning the existing run. A dropped connection or retry gives you back the run you already started, not a duplicate. |
| Same key, a different request            | `409` (`idempotency-conflict`). One key maps to one request.                                                                    |

Without a key, every create makes a new run; there is no automatic dedup. Reuse a key only when you mean to retry the exact same request.

## Errors

Reporting API errors use RFC 7807 `application/problem+json`, except authentication failures, which use `401 application/json` with a `detail` field. Match problem responses on the trailing `<code>` segment of `type`.

| Code                    | HTTP | When                                                                 |
| ----------------------- | ---- | -------------------------------------------------------------------- |
| `invalid-parameter`     | 400  | A bad parameter, like an interval longer than 92 days.               |
| `invalid-column`        | 400  | A requested column is not in the report type's catalog.              |
| —                       | 401  | Token missing or invalid; handle by HTTP status.                     |
| `report-type-not-found` | 404  | Unknown report type.                                                 |
| `data-unavailable`      | 404  | Interval outside the report's available window.                      |
| `run-not-found`         | 404  | Unknown run id.                                                      |
| `idempotency-conflict`  | 409  | A used `idempotency_key` reused with a different request.            |
| `validation-error`      | 422  | Malformed body, an unknown field, or `end` not greater than `start`. |
| `service-unavailable`   | 503  | Temporary; retry.                                                    |

Runs with `status: FAILED` return `200 OK` with `status: FAILED` and an `error.code`:

| `error.code`            | Meaning                                     |
| ----------------------- | ------------------------------------------- |
| `invalid_run_request`   | The request was invalid.                    |
| `run_timeout`           | The run exceeded the processing time limit. |
| `run_processing_failed` | Data processing failed.                     |
| `service_unavailable`   | The service was temporarily unavailable.    |
| `run_failed`            | The run couldn't be completed.              |

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/reporting-api/quickstart">Run your first report end to end.</Card>
  <Card title="API reference" icon="code" href="/api-reference/report-runs/create-report-run">Browse every endpoint and schema.</Card>
</CardGroup>


## Related topics

- [API Reference Overview](/api-reference/overview.md)
- [MCP Server](/guides/mcp-server.md)
- [Appendix](/partner-integration/appendix.md)
