# API overview

The Shipment Insight API will let your systems create shipments, read tracking and predictions, and manage webhooks over REST and JSON. This page is a preview of how it will work; details may change before launch.

> **Preview:** The public API is not available yet. Everything here describes the planned design so you can prepare your integration. Endpoints and fields may still change before launch.

## Basics

|  |  |
| --- | --- |
| Style | REST, with JSON request and response bodies. |
| Base URL | `https://api.example.com/v1` (a placeholder: the real address will be announced at launch). |
| Format | UTF-8 JSON. Times are ISO 8601 in UTC, for example `2026-11-07T18:40:00Z`. |
| IDs | Opaque strings. Store them as text and don't parse them. |
| Specification | An OpenAPI 3.1 file will be published with the API. |

## Authentication with API keys

Every request needs an API key in the `Authorization` header. Admins will create keys in **Settings**. Each key has **scopes** that limit what it can do, for example reading shipments but not changing them.

| Prefix | Use |
| --- | --- |
| `si_live_` | Your real data. |
| `si_test_` | A sandbox with test data. Test keys never touch live data. |

```bash
curl https://api.example.com/v1/shipments/shp_0123456789 \
  -H "Authorization: Bearer $SI_API_KEY"
```

> **Keep keys secret:** A key is shown only once, when it's created. Store it in a secrets manager, never in code or in a browser app. If a key leaks, revoke it in **Settings** and create a new one.

- Give each integration its own key with only the scopes it needs.
- Rotate keys with an overlap period, so your integration has no downtime.
- Each key shows when it was last used, which helps you find and remove unused keys.

## Safe retries with idempotency keys

Networks fail. To retry a `POST` without creating a duplicate, send an `Idempotency-Key` header with a unique value (a UUID works well). If the same key arrives again, you get the original response back instead of a second shipment.

```bash
curl https://api.example.com/v1/shipments \
  -X POST \
  -H "Authorization: Bearer $SI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-3b7d-4e1a-9c55-0d2f8e4b7a10" \
  -d '{
    "reference": "LD-880142",
    "mode": "FTL",
    "stops": [
      { "type": "pickup", "facility_id": "fac_montreal_01", "appointment": "2026-11-08T13:00:00Z" },
      { "type": "delivery", "facility_id": "fac_chicago_02", "appointment": "2026-11-09T19:00:00Z" }
    ]
  }'
```

## Pagination

Lists are returned in pages. Use `limit` to choose the page size. Each response includes a `next_cursor`; pass it as `cursor` to get the next page. When `next_cursor` is `null`, you've reached the end.

```bash
curl "https://api.example.com/v1/shipments?status=in_transit&limit=50&cursor=eyJpZCI6..." \
  -H "Authorization: Bearer $SI_API_KEY"
```

```json
{
  "data": [
    { "id": "shp_0123456789", "reference": "LD-880142", "status": "in_transit" }
  ],
  "next_cursor": "eyJpZCI6InNocF8wMTIzNDU2Nzg5In0"
}
```

## Rate limits

Each key can make a limited number of requests per minute. If you go over, you get `429 Too Many Requests` with a `Retry-After` header that says how many seconds to wait. Wait at least that long, then retry, ideally with exponential backoff.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
```

> **TIP:** Instead of polling for changes, subscribe to [webhooks](https://shipmentinsight.com/docs/webhooks). You get updates as they happen and stay well within your limits.

## Errors

Errors use standard HTTP status codes and always return the same JSON shape, with a stable `code` you can handle in your program and a `message` for people. Include the `request_id` when you contact support.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "stops[1].appointment must be after stops[0].appointment",
    "request_id": "req_01HZX4N8Q2"
  }
}
```

| Status | Meaning |
| --- | --- |
| `400` | The request is invalid. Fix it before retrying. |
| `401` | The API key is missing, wrong, expired or revoked. |
| `403` | You can see this resource, but your key's scopes don't allow this action. |
| `404` | Not found, or not available to you. See below. |
| `409` | Conflict, for example the same `Idempotency-Key` reused with a different body. |
| `429` | Too many requests. Wait for `Retry-After`. |
| `5xx` | A problem on our side. Retry with backoff. |

## Why you get 404 instead of 403

If you ask for something that belongs to another organization, or that your key isn't allowed to see, the API answers `404`, exactly as if it didn't exist. This way the API never confirms that someone else's shipment exists. You get `403` only when you can already see the resource but can't perform one specific action on it. The same rule applies in the console: see [Roles and access](https://shipmentinsight.com/docs/roles-and-access#hidden-pages).

## Versioning

- The version is in the path: `/v1`.
- Within a version we only add things: new endpoints, new optional fields, new event types. Your code should ignore fields it doesn't know.
- Breaking changes go into a new version. The old one keeps working for a deprecation period announced in the [Changelog](https://shipmentinsight.com/changelog).