---
title: "Fill this sandbox with a starter catalogue so every other operation has something to answer"
method: POST
path: "/api/v1/sandbox/seed"
tags: ["sandbox"]
---

# Fill this sandbox with a starter catalogue so every other operation has something to answer

`POST /api/v1/sandbox/seed`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/sandbox/seed`
- Auth: HTTP bearer

Writes a small starter set into the sandbox the key belongs to: two locations, two sales channels, two suppliers, three products with two variants each, and a stock level for every variant at both locations. Enough that every published read answers rows and every published write has a real channel, location and variant to name.

Send it with a `tly_test_` key and no body. It answers `200` whether it wrote anything or not: `seeded` says whether this call wrote, `already_seeded` says whether an earlier one did, and `written` counts the rows this call inserted, per table, which is zero once the sandbox is filled. So a partner's CI can call it before every run and read one shape back.

Nothing is truncated, updated or deleted: every row is inserted on conflict do nothing, so a sandbox somebody has since loaded their own data into keeps it. The ids it writes are stable and prefixed `sbx-`, and they are not ids to build against: read your ids off `variants/table` and `inventory/table` as the examples do, because a sandbox seeded with something else holds different ones.

This is not the multi-season demo book. That is composed from bundles by Tightly and comes through support; this is the starter set an integrator needs on their first afternoon.

A live organisation is refused `409`, whatever key was used, and nothing is written.

Scope: `sandbox:write`, which includes `sandbox:read`.

## Headers

- `Tightly-Version` string
- `Idempotency-Key` string

## Request body

- object: No fields. The sandbox is the key's own organisation and the set is fixed.

## Response `200`

The sandbox as this call left it. `written` counts the rows this call inserted, per table, so the first call reports the starter set and every call after it reports zeros beside `already_seeded: true`. One success code either way, because a repeat creates nothing and a client should read what happened out of the body rather than out of the status.

- object
  - `data` object
    - `already_seeded` boolean: Whether an earlier call had already filled this sandbox.
    - `read_next` string: The read to make next, so the answer names the way on.
    - `seeded` boolean: Whether this call wrote anything.
    - `written` object: Rows this call inserted, keyed by table. Every count is zero on a repeat.
  - `message` object: severity, service and desc.

## Other responses

- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Sandbox for writing; `plan_excludes` when the organisation's plan does not include the public API; `ip_not_allowed` when the caller's address is outside the key's allowlist.
- `409`: This organisation is not a sandbox, so it cannot be seeded, and nothing was written. The sentence names the way out: make a sandbox with POST /api/v1/developer/sandbox, mint a test key in it, and send this with that key.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
