# API reference

> Every endpoint of the LayerCrew Partner API, grouped by tag with parameters, request and response fields and examples, generated from the OpenAPI document.

Updated 2026-10-24 · HTML: https://layercrew.com/developers/reference · Markdown: https://layercrew.com/developers/reference.md · OpenAPI: https://layercrew.com/openapi.json

Every endpoint of the Partner API, generated from the same OpenAPI 3.1 document the engine serves at `GET /v1/openapi.json` and this site at [/openapi.json](https://layercrew.com/openapi.json). Load that URL into your client generator or API tool — it carries CORS headers — or read it here.

## Conventions

- Base URL `https://engine.layercrew.com/v1`; HTTPS only; `Authorization: Bearer <key>` on every route except the spec itself.
- Envelope: `{"data": …}` for one object, `{"data": [...], "meta": {page, per_page, total, total_pages}}` for lists, `{"error": {code, message, details?}}` for errors.
- Money is a string with two decimals in EUR. Timestamps are RFC 3339 in UTC. Ids are UUIDs; an order's number (`PA-YYYY-NNNN`) is accepted wherever its id is.
- Lists take `page` and `per_page` (25 by default, 100 at most) and return newest first. Event feeds take `after` (a `seq`) and `limit` (50 by default, 200 at most) and return oldest first.
- Every response carries `X-Request-ID`. Rate-limited responses carry `Retry-After`, `X-RateLimit-Limit` and `X-RateLimit-Remaining`.
- Request bodies are JSON up to 256 KiB. `POST /v1/orders` requires an `Idempotency-Key`; other creates accept one.
- The API version is `2026-10-24`; every event carries it as `api_version`.

## Account

Who you are, your plan and your usage.

### GET /v1/ping

**Check your key**

Confirms the key works and tells you which partner, environment and plan it belongs to.

Responses:

- `200` — The key is valid.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).
- `403` — The partner account is not active (`PARTNER_INACTIVE`) or the route is test-only (`TEST_ONLY`).
- `429` — Too many requests (`RATE_LIMITED`) or a monthly cap reached (`QUOTA_EXCEEDED`). Honour `Retry-After`.

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `partner` | object | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `key_id` | string | yes |  |
| `plan` | object | yes |  |
| `rate_limit` | object | yes |  |
| `server_time` | string (date-time) | yes |  |

```json
{
  "data": {
    "partner": {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "number": "LC-PT-0007",
      "name": "Claire Martin"
    },
    "environment": "live",
    "key_id": "k7m2p9q4r1s8t3u6",
    "plan": {
      "key": "key",
      "name": "Claire Martin"
    },
    "rate_limit": {
      "per_second": 2,
      "burst": 10
    },
    "server_time": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/ping" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/account

**Your plan, limits, usage and agreement status**

Responses:

- `200` — The account.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).
- `429` — Too many requests (`RATE_LIMITED`) or a monthly cap reached (`QUOTA_EXCEEDED`). Honour `Retry-After`.

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `partner` | object | yes |  |
| `status` | enum: `pending_verification`, `pending_agreement`, `active`, `suspended`, `closed` | yes |  |
| `plan` | object | yes |  |
| `limits` | PlanLimits | yes | The limits in force for you (plan plus any agreed overrides). |
| `usage` | object | yes |  |
| `agreements` | array of object | yes |  |

```json
{
  "data": {
    "partner": {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "number": "number",
      "name": "Claire Martin",
      "country_code": "FR",
      "locale": "en"
    },
    "status": "pending_verification",
    "plan": {
      "key": "key",
      "name": "Claire Martin"
    },
    "limits": {
      "discount_pct": 1.5,
      "rate_per_second": 2,
      "burst": 10,
      "monthly_request_cap": 0,
      "monthly_order_cap": 0,
      "monthly_test_order_cap": 0,
      "max_file_bytes": 67108864,
      "max_files": 0,
      "max_items_per_order": 0,
      "max_test_items_per_order": 0,
      "max_quantity_per_item": 0,
      "allowed_materials": [
        "allowed materials"
      ],
      "allowed_nozzles": [
        "allowed nozzles"
      ],
      "ship_before_payment": false,
      "webhook_endpoints_max": 0,
      "webhook_max_attempts": 12,
      "test_file_retention_days": 0,
      "file_retention_days_after_last_order": 0,
      "recipient_retention_days": 90,
      "quote_daily_cap": 0
    },
    "usage": {
      "month": "2026-10",
      "requests": 318,
      "orders_live": 4,
      "orders_test": 11,
      "quotes_today": 2
    },
    "agreements": [
      {
        "document": "integration_agreement",
        "version": "2026-10-24",
        "status": "none",
        "accepted_at": "2026-10-24T09:30:00Z",
        "countersigned_at": "2026-10-24T09:30:00Z"
      }
    ]
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/account" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/openapi.json

**This document**

No authentication.

Responses:

- `200` — The OpenAPI 3.1 description of this API.

```json
{}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/openapi.json"
```

## Materials

What you can order parts in.

### GET /v1/materials

**Materials, nozzles and colours you can order**

The list changes as the catalogue changes. Order with a `material` from this list; a `colour` is optional and must come from the material's colours when given. `in_stock` is live and never a promise.

Responses:

- `200` — Materials.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `material` | string | yes |  |
| `description` | string | no |  |
| `nozzles` | array of string | yes |  |
| `colours` | array of object | yes |  |

```json
{
  "data": [
    {
      "material": "PLA",
      "description": "Production events",
      "nozzles": [
        "0.4"
      ],
      "colours": [
        {
          "id": "id",
          "name": "Claire Martin",
          "hex": "#111111",
          "in_stock": true
        }
      ]
    }
  ]
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/materials" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Files

Upload the 3D files your orders reference.

### GET /v1/files

**List your files**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no |  |
| `per_page` | query | integer | no |  |
| `status` | query | FileStatus | no |  |

Responses:

- `200` — Files, newest first.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `filename` | string | yes |  |
| `format` | FileFormat: `stl`, `obj`, `3mf` | yes |  |
| `status` | FileStatus: `pending`, `ready`, `rejected`, `purged` | yes |  |
| `size_bytes` | integer | yes |  |
| `sha256` | string | no |  |
| `rejection_code` | enum \| null: `size_mismatch`, `hash_mismatch`, `not_a_model`, `too_complex`, `too_large`, `upload_missing` | no |  |
| `facts` | object | no | What we read from the file: triangles, bounding box in mm, object count, producer. |
| `created_at` | string (date-time) | yes |  |
| `ready_at` | string (date-time) \| null | no |  |
| `purge_after` | string (date-time) \| null | no | When the file will be deleted unless an order uses it. |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "filename": "bracket-v3.stl",
      "format": "stl",
      "status": "pending",
      "size_bytes": 1048576,
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "rejection_code": null,
      "facts": {
        "triangles": 48210,
        "objects": 1,
        "bbox_mm": [
          60,
          42.5,
          18
        ],
        "producer": "Bambu Studio 02.08.02.61",
        "sanitised": false
      },
      "created_at": "2026-10-24T09:30:00Z",
      "ready_at": "2026-10-24T09:30:00Z",
      "purge_after": "2026-10-24T09:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/files?page=1&per_page=25" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/files

**Start an upload**

Declares a file and returns a one-time upload URL. PUT the raw bytes to `upload.url` within `upload.expires_at`, sending exactly the headers in `upload.headers` (both `Content-Type` and `x-goog-content-length-range` are mandatory — the signature covers them). Then call `complete`. Formats: STL, OBJ, 3MF. STEP files are priced by an engineer: send them through https://layercrew.com/contact-us. Size limit: your plan's `max_file_bytes`. Units: millimetres.

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | yes | With its extension: .stl, .obj or .3mf. (up to 200 characters) |
| `size_bytes` | integer | yes | (at least 1) |
| `sha256` | string | yes | Lowercase hex SHA-256 of the exact bytes you will upload. |
| `content_type` | string | no | (default "application/octet-stream") |

```json
{
  "filename": "bracket-v3.stl",
  "size_bytes": 1048576,
  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "content_type": "application/octet-stream"
}
```

Responses:

- `201` — Upload instructions.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).
- `422` — A field is wrong; `error.details` maps field names to messages (`VALIDATION_ERROR`, `IDEMPOTENCY_KEY_REQUIRED`, `MATERIAL_UNAVAILABLE`, `FILE_REJECTED`, `ENDPOINT_URL_INVALID`).
- `429` — Too many requests (`RATE_LIMITED`) or a monthly cap reached (`QUOTA_EXCEEDED`). Honour `Retry-After`.

Response `201`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `filename` | string | yes |  |
| `format` | FileFormat: `stl`, `obj`, `3mf` | yes |  |
| `status` | FileStatus: `pending`, `ready`, `rejected`, `purged` | yes |  |
| `size_bytes` | integer | yes |  |
| `sha256` | string | no |  |
| `rejection_code` | enum \| null: `size_mismatch`, `hash_mismatch`, `not_a_model`, `too_complex`, `too_large`, `upload_missing` | no |  |
| `facts` | object | no | What we read from the file: triangles, bounding box in mm, object count, producer. |
| `created_at` | string (date-time) | yes |  |
| `ready_at` | string (date-time) \| null | no |  |
| `purge_after` | string (date-time) \| null | no | When the file will be deleted unless an order uses it. |
| `upload` | object | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "filename": "bracket-v3.stl",
    "format": "stl",
    "status": "pending",
    "size_bytes": 1048576,
    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "rejection_code": null,
    "facts": {
      "triangles": 48210,
      "objects": 1,
      "bbox_mm": [
        60,
        42.5,
        18
      ],
      "producer": "Bambu Studio 02.08.02.61",
      "sanitised": false
    },
    "created_at": "2026-10-24T09:30:00Z",
    "ready_at": "2026-10-24T09:30:00Z",
    "purge_after": "2026-10-24T09:30:00Z",
    "upload": {
      "method": "PUT",
      "url": "https://shop.example.com/webhooks/layercrew",
      "headers": {
        "Content-Type": "application/octet-stream",
        "x-goog-content-length-range": "0,1048576"
      },
      "expires_at": "2026-10-24T09:30:00Z"
    }
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/files" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "bracket-v3.stl",
  "size_bytes": 1048576,
  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "content_type": "application/octet-stream"
}'
```
### GET /v1/files/{file_id}

**Get a file**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The file.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `filename` | string | yes |  |
| `format` | FileFormat: `stl`, `obj`, `3mf` | yes |  |
| `status` | FileStatus: `pending`, `ready`, `rejected`, `purged` | yes |  |
| `size_bytes` | integer | yes |  |
| `sha256` | string | no |  |
| `rejection_code` | enum \| null: `size_mismatch`, `hash_mismatch`, `not_a_model`, `too_complex`, `too_large`, `upload_missing` | no |  |
| `facts` | object | no | What we read from the file: triangles, bounding box in mm, object count, producer. |
| `created_at` | string (date-time) | yes |  |
| `ready_at` | string (date-time) \| null | no |  |
| `purge_after` | string (date-time) \| null | no | When the file will be deleted unless an order uses it. |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "filename": "bracket-v3.stl",
    "format": "stl",
    "status": "pending",
    "size_bytes": 1048576,
    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "rejection_code": null,
    "facts": {
      "triangles": 48210,
      "objects": 1,
      "bbox_mm": [
        60,
        42.5,
        18
      ],
      "producer": "Bambu Studio 02.08.02.61",
      "sanitised": false
    },
    "created_at": "2026-10-24T09:30:00Z",
    "ready_at": "2026-10-24T09:30:00Z",
    "purge_after": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/files/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### DELETE /v1/files/{file_id}

**Delete a file**

A file no order uses is deleted now. A file used only by finished orders is scheduled for deletion within the hour (202). A file an open order, a pending quote or a running price job still needs answers 409 `FILE_IN_USE` and names it.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file_id` | path | string (uuid) | yes |  |

Responses:

- `202` — Scheduled for deletion.
- `204` — Deleted.
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).

```bash
curl -X DELETE "https://engine.layercrew.com/v1/files/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/files/{file_id}/complete

**Finish an upload**

Verifies the uploaded bytes against the declared size and SHA-256, reads the model, strips any embedded slicer scripts from a 3MF, and stores the canonical copy. Idempotent: calling it again on a ready file returns the file. A file that fails verification is `rejected` with a `rejection_code`; upload a corrected file.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The file is ready.
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — Another completion of this file is running.
- `422` — The file was rejected; `error.details.rejection_code` says why.

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `filename` | string | yes |  |
| `format` | FileFormat: `stl`, `obj`, `3mf` | yes |  |
| `status` | FileStatus: `pending`, `ready`, `rejected`, `purged` | yes |  |
| `size_bytes` | integer | yes |  |
| `sha256` | string | no |  |
| `rejection_code` | enum \| null: `size_mismatch`, `hash_mismatch`, `not_a_model`, `too_complex`, `too_large`, `upload_missing` | no |  |
| `facts` | object | no | What we read from the file: triangles, bounding box in mm, object count, producer. |
| `created_at` | string (date-time) | yes |  |
| `ready_at` | string (date-time) \| null | no |  |
| `purge_after` | string (date-time) \| null | no | When the file will be deleted unless an order uses it. |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "filename": "bracket-v3.stl",
    "format": "stl",
    "status": "pending",
    "size_bytes": 1048576,
    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "rejection_code": null,
    "facts": {
      "triangles": 48210,
      "objects": 1,
      "bbox_mm": [
        60,
        42.5,
        18
      ],
      "producer": "Bambu Studio 02.08.02.61",
      "sanitised": false
    },
    "created_at": "2026-10-24T09:30:00Z",
    "ready_at": "2026-10-24T09:30:00Z",
    "purge_after": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/files/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/complete" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Quotes

Price a file before you order it.

### GET /v1/quotes

**List your quotes**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no |  |
| `per_page` | query | integer | no |  |

Responses:

- `200` — Quotes, newest first.

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `file_id` | string (uuid) | yes |  |
| `material` | string | yes |  |
| `nozzle` | string | yes |  |
| `quantity` | integer | yes |  |
| `status` | enum: `pending`, `priced`, `failed` | yes |  |
| `unit_price` | Money | no | EUR with two decimals, as a string. |
| `list_unit_price` | Money | no | EUR with two decimals, as a string. |
| `discount_pct` | string | no |  |
| `total` | Money | no | EUR with two decimals, as a string. |
| `currency` | enum: `EUR` | no |  |
| `tiers` | array of object | no | Unit price at other quantities. |
| `estimate` | PriceEstimate | no | What the slicer measured and the settings it used. |
| `failure_code` | enum \| null: `model_too_large`, `not_a_model`, `material_unavailable`, `slicer_rejected`, `file_unavailable` | no |  |
| `failure_message` | string \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `priced_at` | string (date-time) \| null | no |  |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "material": "PLA",
      "nozzle": "0.4",
      "quantity": 2,
      "status": "pending",
      "unit_price": "12.40",
      "list_unit_price": "12.40",
      "discount_pct": "0",
      "total": "12.40",
      "currency": "EUR",
      "tiers": [
        {
          "min_quantity": 1,
          "unit_price": "12.40"
        }
      ],
      "estimate": {
        "weight_grams": 31.2,
        "time_seconds": 5400,
        "unit_yield": 1,
        "printer_model": "Bambu Lab A2L",
        "nozzle": "0.4",
        "material": "PLA",
        "infill_pct": 15,
        "wall_loops": 3,
        "support": "auto",
        "warnings": [
          "warnings"
        ],
        "priced_at": "2026-10-24T09:30:00Z"
      },
      "failure_code": null,
      "failure_message": null,
      "created_at": "2026-10-24T09:30:00Z",
      "priced_at": "2026-10-24T09:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/quotes?page=1&per_page=25" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/quotes

**Price a file**

Prices one ready file in one material at one quantity. Pricing takes seconds to a few minutes (the file is sliced for real); poll the quote or listen for `quote.priced`. The price is the one an order of the same file, material and quantity would carry today; it is frozen on an order only when the order is accepted.

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file_id` | string (uuid) | yes |  |
| `material` | string | yes |  |
| `nozzle` | string | no | (default "0.4") |
| `quantity` | integer | yes | (at least 1) |

```json
{
  "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "material": "PLA",
  "nozzle": "0.4",
  "quantity": 2
}
```

Responses:

- `202` — Pricing started.
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).
- `422` — A field is wrong; `error.details` maps field names to messages (`VALIDATION_ERROR`, `IDEMPOTENCY_KEY_REQUIRED`, `MATERIAL_UNAVAILABLE`, `FILE_REJECTED`, `ENDPOINT_URL_INVALID`).
- `429` — Too many requests (`RATE_LIMITED`) or a monthly cap reached (`QUOTA_EXCEEDED`). Honour `Retry-After`.

Response `202`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `file_id` | string (uuid) | yes |  |
| `material` | string | yes |  |
| `nozzle` | string | yes |  |
| `quantity` | integer | yes |  |
| `status` | enum: `pending`, `priced`, `failed` | yes |  |
| `unit_price` | Money | no | EUR with two decimals, as a string. |
| `list_unit_price` | Money | no | EUR with two decimals, as a string. |
| `discount_pct` | string | no |  |
| `total` | Money | no | EUR with two decimals, as a string. |
| `currency` | enum: `EUR` | no |  |
| `tiers` | array of object | no | Unit price at other quantities. |
| `estimate` | PriceEstimate | no | What the slicer measured and the settings it used. |
| `failure_code` | enum \| null: `model_too_large`, `not_a_model`, `material_unavailable`, `slicer_rejected`, `file_unavailable` | no |  |
| `failure_message` | string \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `priced_at` | string (date-time) \| null | no |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "material": "PLA",
    "nozzle": "0.4",
    "quantity": 2,
    "status": "pending",
    "unit_price": "12.40",
    "list_unit_price": "12.40",
    "discount_pct": "0",
    "total": "12.40",
    "currency": "EUR",
    "tiers": [
      {
        "min_quantity": 1,
        "unit_price": "12.40"
      }
    ],
    "estimate": {
      "weight_grams": 31.2,
      "time_seconds": 5400,
      "unit_yield": 1,
      "printer_model": "Bambu Lab A2L",
      "nozzle": "0.4",
      "material": "PLA",
      "infill_pct": 15,
      "wall_loops": 3,
      "support": "auto",
      "warnings": [
        "warnings"
      ],
      "priced_at": "2026-10-24T09:30:00Z"
    },
    "failure_code": null,
    "failure_message": null,
    "created_at": "2026-10-24T09:30:00Z",
    "priced_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/quotes" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "material": "PLA",
  "nozzle": "0.4",
  "quantity": 2
}'
```
### GET /v1/quotes/{quote_id}

**Get a quote**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `quote_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The quote.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `file_id` | string (uuid) | yes |  |
| `material` | string | yes |  |
| `nozzle` | string | yes |  |
| `quantity` | integer | yes |  |
| `status` | enum: `pending`, `priced`, `failed` | yes |  |
| `unit_price` | Money | no | EUR with two decimals, as a string. |
| `list_unit_price` | Money | no | EUR with two decimals, as a string. |
| `discount_pct` | string | no |  |
| `total` | Money | no | EUR with two decimals, as a string. |
| `currency` | enum: `EUR` | no |  |
| `tiers` | array of object | no | Unit price at other quantities. |
| `estimate` | PriceEstimate | no | What the slicer measured and the settings it used. |
| `failure_code` | enum \| null: `model_too_large`, `not_a_model`, `material_unavailable`, `slicer_rejected`, `file_unavailable` | no |  |
| `failure_message` | string \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `priced_at` | string (date-time) \| null | no |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "material": "PLA",
    "nozzle": "0.4",
    "quantity": 2,
    "status": "pending",
    "unit_price": "12.40",
    "list_unit_price": "12.40",
    "discount_pct": "0",
    "total": "12.40",
    "currency": "EUR",
    "tiers": [
      {
        "min_quantity": 1,
        "unit_price": "12.40"
      }
    ],
    "estimate": {
      "weight_grams": 31.2,
      "time_seconds": 5400,
      "unit_yield": 1,
      "printer_model": "Bambu Lab A2L",
      "nozzle": "0.4",
      "material": "PLA",
      "infill_pct": 15,
      "wall_loops": 3,
      "support": "auto",
      "warnings": [
        "warnings"
      ],
      "priced_at": "2026-10-24T09:30:00Z"
    },
    "failure_code": null,
    "failure_message": null,
    "created_at": "2026-10-24T09:30:00Z",
    "priced_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/quotes/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Orders

Create and follow production orders.

### GET /v1/orders

**List your orders**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no |  |
| `per_page` | query | integer | no |  |
| `status` | query | OrderStatus | no |  |
| `reference` | query | string | no | Exact match on your reference. |
| `created_after` | query | string (date-time) | no |  |

Responses:

- `200` — Orders, newest first.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `reference` | string | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `status` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `pricing_status` | enum: `pending`, `priced`, `failed` | yes |  |
| `items` | array of OrderItem | yes |  |
| `pricing` | Pricing | yes |  |
| `payment` | Payment | no |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | yes | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `metadata` | object | yes |  |
| `notes` | string | no |  |
| `shipments` | array of Shipment | yes |  |
| `invoices` | array of Invoice | yes |  |
| `rejection` | Rejection \| null | no |  |
| `hold` | Hold \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `accepted_at` | string (date-time) \| null | no |  |
| `shipped_at` | string (date-time) \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `cancelled_at` | string (date-time) \| null | no |  |
| `links` | object | yes |  |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "number": "PA-2026-0007",
      "reference": "SHOP-10231",
      "environment": "live",
      "status": "received",
      "pricing_status": "pending",
      "items": [
        {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "position": 1,
          "file": {
            "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
            "filename": "bracket-v3.stl",
            "format": "stl"
          },
          "quantity": 2,
          "material": "PLA",
          "colour": "black",
          "nozzle": "0.4",
          "notes": "Fit check before the batch, please.",
          "pricing_status": "pending",
          "list_unit_price": "12.40",
          "discount_pct": "0",
          "unit_price": "12.40",
          "line_total": "12.40",
          "estimate": {
            "weight_grams": 31.2,
            "time_seconds": 5400,
            "unit_yield": 1,
            "printer_model": "Bambu Lab A2L",
            "nozzle": "0.4",
            "material": "PLA",
            "infill_pct": 15,
            "wall_loops": 3,
            "support": "auto",
            "warnings": [],
            "priced_at": "2026-10-24T09:30:00Z"
          },
          "failure_code": null,
          "failure_message": null
        }
      ],
      "pricing": {
        "currency": "EUR",
        "subtotal": "12.40",
        "discount_total": "12.40",
        "shipping_total": "12.40",
        "tax_total": "12.40",
        "tax_treatment": "standard",
        "grand_total": "12.40",
        "final": true
      },
      "payment": {
        "method": "bank_transfer",
        "status": "unpaid",
        "ship_before_payment": false,
        "amount_due": "12.40",
        "instructions": {
          "beneficiary": "LAYER CREW SRL",
          "iban": "RO49AAAA1B31007593840000",
          "bank": "Example Bank",
          "reference": "SHOP-10231"
        },
        "paid_at": "2026-10-24T09:30:00Z"
      },
      "delivery": {
        "name": "Claire Martin",
        "company": "Atelier Martin",
        "phone": "+33 6 12 34 56 78",
        "email": "claire@example.com",
        "line1": "12 rue de la Paix",
        "line2": "Bâtiment B",
        "city": "Lyon",
        "region": "Auvergne-Rhône-Alpes",
        "postal_code": "69001",
        "country_code": "FR"
      },
      "white_label": {
        "enabled": true,
        "sender_name": "Studio NS",
        "card_text": "Thank you for your order — Studio NS",
        "packaging_preset": "neutral",
        "no_invoice_in_box": true,
        "language": "fr"
      },
      "metadata": {
        "shop_order_id": "10231"
      },
      "notes": "Fit check before the batch, please.",
      "shipments": [
        {
          "carrier": "FAN Courier",
          "tracking_number": "2057100012345",
          "tracking_url": "https://www.fancourier.ro/awb-tracking/?awb=2057100012345",
          "status": "shipped",
          "last_tracking_status": "last tracking status",
          "shipped_at": "2026-10-24T09:30:00Z",
          "delivered_at": "2026-10-24T09:30:00Z"
        }
      ],
      "invoices": [
        {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "number": "number",
          "kind": "fiscal",
          "issued_at": "2026-10-24T09:30:00Z"
        }
      ],
      "rejection": null,
      "hold": null,
      "created_at": "2026-10-24T09:30:00Z",
      "updated_at": "2026-10-24T09:30:00Z",
      "accepted_at": "2026-10-24T09:30:00Z",
      "shipped_at": "2026-10-24T09:30:00Z",
      "delivered_at": "2026-10-24T09:30:00Z",
      "cancelled_at": "2026-10-24T09:30:00Z",
      "links": {
        "self": "self",
        "events": "events"
      }
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/orders?page=1&per_page=25" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/orders

**Create an order**

Creates a production order from ready files. Requires an `Idempotency-Key` header (any string up to 255 characters, unique per attempt; a retry with the same key and the same body returns the first response with `Idempotent-Replayed: true`). Your `reference` must be unique among your orders in this environment; a duplicate answers 409 `REFERENCE_TAKEN` with the existing order id. The order is priced within minutes (`order.priced`), then a LayerCrew engineer accepts it (`order.accepted`, usually within one business day) or declines it with a reason you can act on (`order.rejected`). Payment instructions appear on acceptance. Production starts after payment unless your plan says otherwise.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | Unique per attempt, up to 255 characters of `[A-Za-z0-9._:~-]`. Kept 24 hours. |

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reference` | string | yes | Your own order reference; unique per environment. (up to 120 characters) |
| `items` | array of OrderItemCreate | yes |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | no | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `notes` | string | no | (up to 2000 characters) |
| `metadata` | object | no | Returned untouched on the order and in every webhook. (up to 20 keys) |

```json
{
  "reference": "SHOP-10231",
  "items": [
    {
      "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "quantity": 2,
      "material": "PLA",
      "colour": "black",
      "nozzle": "0.4",
      "notes": "Fit check before the batch, please."
    }
  ],
  "delivery": {
    "name": "Claire Martin",
    "company": "Atelier Martin",
    "phone": "+33 6 12 34 56 78",
    "email": "claire@example.com",
    "line1": "12 rue de la Paix",
    "line2": "Bâtiment B",
    "city": "Lyon",
    "region": "Auvergne-Rhône-Alpes",
    "postal_code": "69001",
    "country_code": "FR"
  },
  "white_label": {
    "enabled": true,
    "sender_name": "Studio NS",
    "card_text": "Thank you for your order — Studio NS",
    "packaging_preset": "neutral",
    "no_invoice_in_box": true,
    "language": "fr"
  },
  "notes": "Fit check before the batch, please.",
  "metadata": {
    "shop_order_id": "10231"
  }
}
```

Responses:

- `200` — Replayed from a previous request with the same `Idempotency-Key`.
- `202` — Order received; pricing started.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).
- `422` — A field is wrong; `error.details` maps field names to messages (`VALIDATION_ERROR`, `IDEMPOTENCY_KEY_REQUIRED`, `MATERIAL_UNAVAILABLE`, `FILE_REJECTED`, `ENDPOINT_URL_INVALID`).
- `429` — Too many requests (`RATE_LIMITED`) or a monthly cap reached (`QUOTA_EXCEEDED`). Honour `Retry-After`.

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `reference` | string | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `status` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `pricing_status` | enum: `pending`, `priced`, `failed` | yes |  |
| `items` | array of OrderItem | yes |  |
| `pricing` | Pricing | yes |  |
| `payment` | Payment | no |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | yes | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `metadata` | object | yes |  |
| `notes` | string | no |  |
| `shipments` | array of Shipment | yes |  |
| `invoices` | array of Invoice | yes |  |
| `rejection` | Rejection \| null | no |  |
| `hold` | Hold \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `accepted_at` | string (date-time) \| null | no |  |
| `shipped_at` | string (date-time) \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `cancelled_at` | string (date-time) \| null | no |  |
| `links` | object | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "number": "PA-2026-0007",
    "reference": "SHOP-10231",
    "environment": "live",
    "status": "received",
    "pricing_status": "pending",
    "items": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "position": 1,
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl"
        },
        "quantity": 2,
        "material": "PLA",
        "colour": "black",
        "nozzle": "0.4",
        "notes": "Fit check before the batch, please.",
        "pricing_status": "pending",
        "list_unit_price": "12.40",
        "discount_pct": "0",
        "unit_price": "12.40",
        "line_total": "12.40",
        "estimate": {
          "weight_grams": 31.2,
          "time_seconds": 5400,
          "unit_yield": 1,
          "printer_model": "Bambu Lab A2L",
          "nozzle": "0.4",
          "material": "PLA",
          "infill_pct": 15,
          "wall_loops": 3,
          "support": "auto",
          "warnings": [
            "warnings"
          ],
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "failure_code": null,
        "failure_message": null
      }
    ],
    "pricing": {
      "currency": "EUR",
      "subtotal": "12.40",
      "discount_total": "12.40",
      "shipping_total": "12.40",
      "tax_total": "12.40",
      "tax_treatment": "standard",
      "grand_total": "12.40",
      "final": true
    },
    "payment": {
      "method": "bank_transfer",
      "status": "unpaid",
      "ship_before_payment": false,
      "amount_due": "12.40",
      "instructions": {
        "beneficiary": "LAYER CREW SRL",
        "iban": "RO49AAAA1B31007593840000",
        "bank": "Example Bank",
        "reference": "SHOP-10231"
      },
      "paid_at": "2026-10-24T09:30:00Z"
    },
    "delivery": {
      "name": "Claire Martin",
      "company": "Atelier Martin",
      "phone": "+33 6 12 34 56 78",
      "email": "claire@example.com",
      "line1": "12 rue de la Paix",
      "line2": "Bâtiment B",
      "city": "Lyon",
      "region": "Auvergne-Rhône-Alpes",
      "postal_code": "69001",
      "country_code": "FR"
    },
    "white_label": {
      "enabled": true,
      "sender_name": "Studio NS",
      "card_text": "Thank you for your order — Studio NS",
      "packaging_preset": "neutral",
      "no_invoice_in_box": true,
      "language": "fr"
    },
    "metadata": {
      "shop_order_id": "10231"
    },
    "notes": "Fit check before the batch, please.",
    "shipments": [
      {
        "carrier": "FAN Courier",
        "tracking_number": "2057100012345",
        "tracking_url": "https://www.fancourier.ro/awb-tracking/?awb=2057100012345",
        "status": "shipped",
        "last_tracking_status": "last tracking status",
        "shipped_at": "2026-10-24T09:30:00Z",
        "delivered_at": "2026-10-24T09:30:00Z"
      }
    ],
    "invoices": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "number": "number",
        "kind": "fiscal",
        "issued_at": "2026-10-24T09:30:00Z"
      }
    ],
    "rejection": null,
    "hold": null,
    "created_at": "2026-10-24T09:30:00Z",
    "updated_at": "2026-10-24T09:30:00Z",
    "accepted_at": "2026-10-24T09:30:00Z",
    "shipped_at": "2026-10-24T09:30:00Z",
    "delivered_at": "2026-10-24T09:30:00Z",
    "cancelled_at": "2026-10-24T09:30:00Z",
    "links": {
      "self": "self",
      "events": "events"
    }
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/orders" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "reference": "SHOP-10231",
  "items": [
    {
      "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "quantity": 2,
      "material": "PLA",
      "colour": "black",
      "nozzle": "0.4",
      "notes": "Fit check before the batch, please."
    }
  ],
  "delivery": {
    "name": "Claire Martin",
    "company": "Atelier Martin",
    "phone": "+33 6 12 34 56 78",
    "email": "claire@example.com",
    "line1": "12 rue de la Paix",
    "line2": "Bâtiment B",
    "city": "Lyon",
    "region": "Auvergne-Rhône-Alpes",
    "postal_code": "69001",
    "country_code": "FR"
  },
  "white_label": {
    "enabled": true,
    "sender_name": "Studio NS",
    "card_text": "Thank you for your order — Studio NS",
    "packaging_preset": "neutral",
    "no_invoice_in_box": true,
    "language": "fr"
  },
  "notes": "Fit check before the batch, please.",
  "metadata": {
    "shop_order_id": "10231"
  }
}'
```
### GET /v1/orders/{order_id}

**Get an order**

`order_id` is the order's id or its number (`PA-2026-0007`).

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |

Responses:

- `200` — The order.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `reference` | string | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `status` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `pricing_status` | enum: `pending`, `priced`, `failed` | yes |  |
| `items` | array of OrderItem | yes |  |
| `pricing` | Pricing | yes |  |
| `payment` | Payment | no |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | yes | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `metadata` | object | yes |  |
| `notes` | string | no |  |
| `shipments` | array of Shipment | yes |  |
| `invoices` | array of Invoice | yes |  |
| `rejection` | Rejection \| null | no |  |
| `hold` | Hold \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `accepted_at` | string (date-time) \| null | no |  |
| `shipped_at` | string (date-time) \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `cancelled_at` | string (date-time) \| null | no |  |
| `links` | object | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "number": "PA-2026-0007",
    "reference": "SHOP-10231",
    "environment": "live",
    "status": "received",
    "pricing_status": "pending",
    "items": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "position": 1,
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl"
        },
        "quantity": 2,
        "material": "PLA",
        "colour": "black",
        "nozzle": "0.4",
        "notes": "Fit check before the batch, please.",
        "pricing_status": "pending",
        "list_unit_price": "12.40",
        "discount_pct": "0",
        "unit_price": "12.40",
        "line_total": "12.40",
        "estimate": {
          "weight_grams": 31.2,
          "time_seconds": 5400,
          "unit_yield": 1,
          "printer_model": "Bambu Lab A2L",
          "nozzle": "0.4",
          "material": "PLA",
          "infill_pct": 15,
          "wall_loops": 3,
          "support": "auto",
          "warnings": [
            "warnings"
          ],
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "failure_code": null,
        "failure_message": null
      }
    ],
    "pricing": {
      "currency": "EUR",
      "subtotal": "12.40",
      "discount_total": "12.40",
      "shipping_total": "12.40",
      "tax_total": "12.40",
      "tax_treatment": "standard",
      "grand_total": "12.40",
      "final": true
    },
    "payment": {
      "method": "bank_transfer",
      "status": "unpaid",
      "ship_before_payment": false,
      "amount_due": "12.40",
      "instructions": {
        "beneficiary": "LAYER CREW SRL",
        "iban": "RO49AAAA1B31007593840000",
        "bank": "Example Bank",
        "reference": "SHOP-10231"
      },
      "paid_at": "2026-10-24T09:30:00Z"
    },
    "delivery": {
      "name": "Claire Martin",
      "company": "Atelier Martin",
      "phone": "+33 6 12 34 56 78",
      "email": "claire@example.com",
      "line1": "12 rue de la Paix",
      "line2": "Bâtiment B",
      "city": "Lyon",
      "region": "Auvergne-Rhône-Alpes",
      "postal_code": "69001",
      "country_code": "FR"
    },
    "white_label": {
      "enabled": true,
      "sender_name": "Studio NS",
      "card_text": "Thank you for your order — Studio NS",
      "packaging_preset": "neutral",
      "no_invoice_in_box": true,
      "language": "fr"
    },
    "metadata": {
      "shop_order_id": "10231"
    },
    "notes": "Fit check before the batch, please.",
    "shipments": [
      {
        "carrier": "FAN Courier",
        "tracking_number": "2057100012345",
        "tracking_url": "https://www.fancourier.ro/awb-tracking/?awb=2057100012345",
        "status": "shipped",
        "last_tracking_status": "last tracking status",
        "shipped_at": "2026-10-24T09:30:00Z",
        "delivered_at": "2026-10-24T09:30:00Z"
      }
    ],
    "invoices": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "number": "number",
        "kind": "fiscal",
        "issued_at": "2026-10-24T09:30:00Z"
      }
    ],
    "rejection": null,
    "hold": null,
    "created_at": "2026-10-24T09:30:00Z",
    "updated_at": "2026-10-24T09:30:00Z",
    "accepted_at": "2026-10-24T09:30:00Z",
    "shipped_at": "2026-10-24T09:30:00Z",
    "delivered_at": "2026-10-24T09:30:00Z",
    "cancelled_at": "2026-10-24T09:30:00Z",
    "links": {
      "self": "self",
      "events": "events"
    }
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/orders/PA-2026-0007" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/orders/{order_id}/events

**The order's events, in order**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |
| `after` | query | integer | no | Return events with `seq` greater than this. |
| `limit` | query | integer | no |  |

Responses:

- `200` — Events, oldest first.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `type` | EventType: `order.received`, `order.priced`, `order.pricing_failed`, `order.accepted`, `order.rejected`, `order.on_hold`, `order.released`, `order.in_production`, `order.ready_to_ship`, `order.shipped`, `order.tracking_updated`, `order.delivered`, `order.cancelled`, `order.paid`, `order.invoiced`, `order.returned`, `order.recipient_redacted`, `quote.priced`, `quote.failed`, `file.ready`, `file.rejected`, `file.purged`, `agreement.accepted`, `agreement.countersigned`, `key.issued`, `key.revoked`, `webhook_endpoint.disabled`, `webhook_endpoint.secret_rotated`, `partner.suspended`, `partner.activated`, `usage.warning`, `test.ping` | yes |  |
| `api_version` | string | yes |  |
| `created_at` | string (date-time) | yes |  |
| `seq` | integer | yes | Per-partner, gap-free, strictly increasing. |
| `environment` | Environment: `live`, `test` | yes |  |
| `reason_code` | string \| null | no |  |
| `data` | object | yes | `order` for order.* events, `quote` for quote.*, `file` for file.*, `endpoint` for webhook_endpoint.*, `key` for key.* (never the secret), `usage` for usage.warning, `{}` for test.ping. |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "type": "order.received",
      "api_version": "2026-10-24",
      "created_at": "2026-10-24T09:30:00Z",
      "seq": 1234,
      "environment": "live",
      "reason_code": null,
      "data": {
        "order": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "number": "PA-2026-0007",
          "reference": "SHOP-10231",
          "environment": "live",
          "status": "received",
          "pricing_status": "pending",
          "items": [
            {
              "id": null,
              "position": null,
              "file": null,
              "quantity": null,
              "material": null,
              "colour": null,
              "nozzle": null,
              "notes": null,
              "pricing_status": null,
              "list_unit_price": null,
              "discount_pct": null,
              "unit_price": null,
              "line_total": null,
              "estimate": null,
              "failure_code": null,
              "failure_message": null
            }
          ],
          "pricing": {
            "currency": "EUR",
            "subtotal": "12.40",
            "discount_total": "12.40",
            "shipping_total": "12.40",
            "tax_total": "12.40",
            "tax_treatment": "standard",
            "grand_total": "12.40",
            "final": true
          },
          "payment": {
            "method": "bank_transfer",
            "status": "unpaid",
            "ship_before_payment": false,
            "amount_due": "12.40",
            "instructions": {
              "beneficiary": null,
              "iban": null,
              "bank": null,
              "reference": null
            },
            "paid_at": "2026-10-24T09:30:00Z"
          },
          "delivery": {
            "name": "Claire Martin",
            "company": "Atelier Martin",
            "phone": "+33 6 12 34 56 78",
            "email": "claire@example.com",
            "line1": "12 rue de la Paix",
            "line2": "Bâtiment B",
            "city": "Lyon",
            "region": "Auvergne-Rhône-Alpes",
            "postal_code": "69001",
            "country_code": "FR"
          },
          "white_label": {
            "enabled": true,
            "sender_name": "Studio NS",
            "card_text": "Thank you for your order — Studio NS",
            "packaging_preset": "neutral",
            "no_invoice_in_box": true,
            "language": "fr"
          },
          "metadata": {
            "shop_order_id": "10231"
          },
          "notes": "Fit check before the batch, please.",
          "shipments": [
            {
              "carrier": null,
              "tracking_number": null,
              "tracking_url": null,
              "status": null,
              "last_tracking_status": null,
              "shipped_at": null,
              "delivered_at": null
            }
          ],
          "invoices": [
            {
              "id": null,
              "number": null,
              "kind": null,
              "issued_at": null
            }
          ],
          "rejection": null,
          "hold": null,
          "created_at": "2026-10-24T09:30:00Z",
          "updated_at": "2026-10-24T09:30:00Z",
          "accepted_at": "2026-10-24T09:30:00Z",
          "shipped_at": "2026-10-24T09:30:00Z",
          "delivered_at": "2026-10-24T09:30:00Z",
          "cancelled_at": "2026-10-24T09:30:00Z",
          "links": {
            "self": "self",
            "events": "events"
          }
        },
        "quote": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "material": "PLA",
          "nozzle": "0.4",
          "quantity": 2,
          "status": "pending",
          "unit_price": "12.40",
          "list_unit_price": "12.40",
          "discount_pct": "0",
          "total": "12.40",
          "currency": "EUR",
          "tiers": [
            {
              "min_quantity": null,
              "unit_price": null
            }
          ],
          "estimate": {
            "weight_grams": 31.2,
            "time_seconds": 5400,
            "unit_yield": 1,
            "printer_model": "Bambu Lab A2L",
            "nozzle": "0.4",
            "material": "PLA",
            "infill_pct": 15,
            "wall_loops": 3,
            "support": "auto",
            "warnings": [],
            "priced_at": "2026-10-24T09:30:00Z"
          },
          "failure_code": null,
          "failure_message": null,
          "created_at": "2026-10-24T09:30:00Z",
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl",
          "status": "pending",
          "size_bytes": 1048576,
          "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "rejection_code": null,
          "facts": {
            "triangles": 48210,
            "objects": 1,
            "bbox_mm": [
              60,
              42.5,
              18
            ],
            "producer": "Bambu Studio 02.08.02.61",
            "sanitised": false
          },
          "created_at": "2026-10-24T09:30:00Z",
          "ready_at": "2026-10-24T09:30:00Z",
          "purge_after": "2026-10-24T09:30:00Z"
        }
      }
    }
  ],
  "meta": {
    "next_after": 1234,
    "has_more": false
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/orders/PA-2026-0007/events?after=0&limit=50" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/orders/{order_id}/cancel

**Cancel an order**

Allowed while the order is `received`, `pending_review` or `accepted` (before production starts). Later, write to us from the order page and we will tell you what is still possible.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |

Request body (optional):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | no | (up to 500 characters) |

```json
{
  "reason": "Cancelled by the customer before production."
}
```

Responses:

- `200` — Cancelled.
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `reference` | string | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `status` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `pricing_status` | enum: `pending`, `priced`, `failed` | yes |  |
| `items` | array of OrderItem | yes |  |
| `pricing` | Pricing | yes |  |
| `payment` | Payment | no |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | yes | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `metadata` | object | yes |  |
| `notes` | string | no |  |
| `shipments` | array of Shipment | yes |  |
| `invoices` | array of Invoice | yes |  |
| `rejection` | Rejection \| null | no |  |
| `hold` | Hold \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `accepted_at` | string (date-time) \| null | no |  |
| `shipped_at` | string (date-time) \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `cancelled_at` | string (date-time) \| null | no |  |
| `links` | object | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "number": "PA-2026-0007",
    "reference": "SHOP-10231",
    "environment": "live",
    "status": "received",
    "pricing_status": "pending",
    "items": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "position": 1,
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl"
        },
        "quantity": 2,
        "material": "PLA",
        "colour": "black",
        "nozzle": "0.4",
        "notes": "Fit check before the batch, please.",
        "pricing_status": "pending",
        "list_unit_price": "12.40",
        "discount_pct": "0",
        "unit_price": "12.40",
        "line_total": "12.40",
        "estimate": {
          "weight_grams": 31.2,
          "time_seconds": 5400,
          "unit_yield": 1,
          "printer_model": "Bambu Lab A2L",
          "nozzle": "0.4",
          "material": "PLA",
          "infill_pct": 15,
          "wall_loops": 3,
          "support": "auto",
          "warnings": [
            "warnings"
          ],
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "failure_code": null,
        "failure_message": null
      }
    ],
    "pricing": {
      "currency": "EUR",
      "subtotal": "12.40",
      "discount_total": "12.40",
      "shipping_total": "12.40",
      "tax_total": "12.40",
      "tax_treatment": "standard",
      "grand_total": "12.40",
      "final": true
    },
    "payment": {
      "method": "bank_transfer",
      "status": "unpaid",
      "ship_before_payment": false,
      "amount_due": "12.40",
      "instructions": {
        "beneficiary": "LAYER CREW SRL",
        "iban": "RO49AAAA1B31007593840000",
        "bank": "Example Bank",
        "reference": "SHOP-10231"
      },
      "paid_at": "2026-10-24T09:30:00Z"
    },
    "delivery": {
      "name": "Claire Martin",
      "company": "Atelier Martin",
      "phone": "+33 6 12 34 56 78",
      "email": "claire@example.com",
      "line1": "12 rue de la Paix",
      "line2": "Bâtiment B",
      "city": "Lyon",
      "region": "Auvergne-Rhône-Alpes",
      "postal_code": "69001",
      "country_code": "FR"
    },
    "white_label": {
      "enabled": true,
      "sender_name": "Studio NS",
      "card_text": "Thank you for your order — Studio NS",
      "packaging_preset": "neutral",
      "no_invoice_in_box": true,
      "language": "fr"
    },
    "metadata": {
      "shop_order_id": "10231"
    },
    "notes": "Fit check before the batch, please.",
    "shipments": [
      {
        "carrier": "FAN Courier",
        "tracking_number": "2057100012345",
        "tracking_url": "https://www.fancourier.ro/awb-tracking/?awb=2057100012345",
        "status": "shipped",
        "last_tracking_status": "last tracking status",
        "shipped_at": "2026-10-24T09:30:00Z",
        "delivered_at": "2026-10-24T09:30:00Z"
      }
    ],
    "invoices": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "number": "number",
        "kind": "fiscal",
        "issued_at": "2026-10-24T09:30:00Z"
      }
    ],
    "rejection": null,
    "hold": null,
    "created_at": "2026-10-24T09:30:00Z",
    "updated_at": "2026-10-24T09:30:00Z",
    "accepted_at": "2026-10-24T09:30:00Z",
    "shipped_at": "2026-10-24T09:30:00Z",
    "delivered_at": "2026-10-24T09:30:00Z",
    "cancelled_at": "2026-10-24T09:30:00Z",
    "links": {
      "self": "self",
      "events": "events"
    }
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/orders/PA-2026-0007/cancel" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Cancelled by the customer before production."
}'
```
### GET /v1/orders/{order_id}/invoices

**Invoices issued for the order**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |

Responses:

- `200` — Invoices.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `kind` | enum: `fiscal`, `proforma`, `storno` | yes |  |
| `issued_at` | string (date-time) | yes |  |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "number": "number",
      "kind": "fiscal",
      "issued_at": "2026-10-24T09:30:00Z"
    }
  ]
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/orders/PA-2026-0007/invoices" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/orders/{order_id}/invoices/{invoice_id}/download-url

**A 15-minute link to the invoice PDF**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |
| `invoice_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The link.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string (uri) | yes |  |
| `expires_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "url": "https://shop.example.com/webhooks/layercrew",
    "expires_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/orders/PA-2026-0007/invoices/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/download-url" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### DELETE /v1/orders/{order_id}/recipient

**Erase your customer's personal data from a finished order**

Replaces the recipient's name, company, phone, email and street lines with `[redacted]` on the order and on its stored events; city, postal code and country stay for fiscal and zone records. Allowed once the order is delivered, cancelled or rejected. We also do this automatically after the retention window in your plan.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |

Responses:

- `204` — Erased.
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).

```bash
curl -X DELETE "https://engine.layercrew.com/v1/orders/PA-2026-0007/recipient" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Events

The ordered feed of everything that happened to your account.

### GET /v1/events

**Everything that happened, in order**

The authoritative, gap-free sequence of your account's events in this environment. Poll with `after` = the last `seq` you processed. Webhooks carry the same events; this feed is the source of truth when a webhook is late or missed.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `after` | query | integer | no | Return events with `seq` greater than this. |
| `limit` | query | integer | no |  |
| `types` | query | string | no | Comma-separated event types to include. |

Responses:

- `200` — Events, oldest first.
- `401` — The key is missing, malformed, unknown, revoked or for the other environment (`INVALID_API_KEY`, `API_KEY_REVOKED`, `SCOPE_MISMATCH`).

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `type` | EventType: `order.received`, `order.priced`, `order.pricing_failed`, `order.accepted`, `order.rejected`, `order.on_hold`, `order.released`, `order.in_production`, `order.ready_to_ship`, `order.shipped`, `order.tracking_updated`, `order.delivered`, `order.cancelled`, `order.paid`, `order.invoiced`, `order.returned`, `order.recipient_redacted`, `quote.priced`, `quote.failed`, `file.ready`, `file.rejected`, `file.purged`, `agreement.accepted`, `agreement.countersigned`, `key.issued`, `key.revoked`, `webhook_endpoint.disabled`, `webhook_endpoint.secret_rotated`, `partner.suspended`, `partner.activated`, `usage.warning`, `test.ping` | yes |  |
| `api_version` | string | yes |  |
| `created_at` | string (date-time) | yes |  |
| `seq` | integer | yes | Per-partner, gap-free, strictly increasing. |
| `environment` | Environment: `live`, `test` | yes |  |
| `reason_code` | string \| null | no |  |
| `data` | object | yes | `order` for order.* events, `quote` for quote.*, `file` for file.*, `endpoint` for webhook_endpoint.*, `key` for key.* (never the secret), `usage` for usage.warning, `{}` for test.ping. |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "type": "order.received",
      "api_version": "2026-10-24",
      "created_at": "2026-10-24T09:30:00Z",
      "seq": 1234,
      "environment": "live",
      "reason_code": null,
      "data": {
        "order": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "number": "PA-2026-0007",
          "reference": "SHOP-10231",
          "environment": "live",
          "status": "received",
          "pricing_status": "pending",
          "items": [
            {
              "id": null,
              "position": null,
              "file": null,
              "quantity": null,
              "material": null,
              "colour": null,
              "nozzle": null,
              "notes": null,
              "pricing_status": null,
              "list_unit_price": null,
              "discount_pct": null,
              "unit_price": null,
              "line_total": null,
              "estimate": null,
              "failure_code": null,
              "failure_message": null
            }
          ],
          "pricing": {
            "currency": "EUR",
            "subtotal": "12.40",
            "discount_total": "12.40",
            "shipping_total": "12.40",
            "tax_total": "12.40",
            "tax_treatment": "standard",
            "grand_total": "12.40",
            "final": true
          },
          "payment": {
            "method": "bank_transfer",
            "status": "unpaid",
            "ship_before_payment": false,
            "amount_due": "12.40",
            "instructions": {
              "beneficiary": null,
              "iban": null,
              "bank": null,
              "reference": null
            },
            "paid_at": "2026-10-24T09:30:00Z"
          },
          "delivery": {
            "name": "Claire Martin",
            "company": "Atelier Martin",
            "phone": "+33 6 12 34 56 78",
            "email": "claire@example.com",
            "line1": "12 rue de la Paix",
            "line2": "Bâtiment B",
            "city": "Lyon",
            "region": "Auvergne-Rhône-Alpes",
            "postal_code": "69001",
            "country_code": "FR"
          },
          "white_label": {
            "enabled": true,
            "sender_name": "Studio NS",
            "card_text": "Thank you for your order — Studio NS",
            "packaging_preset": "neutral",
            "no_invoice_in_box": true,
            "language": "fr"
          },
          "metadata": {
            "shop_order_id": "10231"
          },
          "notes": "Fit check before the batch, please.",
          "shipments": [
            {
              "carrier": null,
              "tracking_number": null,
              "tracking_url": null,
              "status": null,
              "last_tracking_status": null,
              "shipped_at": null,
              "delivered_at": null
            }
          ],
          "invoices": [
            {
              "id": null,
              "number": null,
              "kind": null,
              "issued_at": null
            }
          ],
          "rejection": null,
          "hold": null,
          "created_at": "2026-10-24T09:30:00Z",
          "updated_at": "2026-10-24T09:30:00Z",
          "accepted_at": "2026-10-24T09:30:00Z",
          "shipped_at": "2026-10-24T09:30:00Z",
          "delivered_at": "2026-10-24T09:30:00Z",
          "cancelled_at": "2026-10-24T09:30:00Z",
          "links": {
            "self": "self",
            "events": "events"
          }
        },
        "quote": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "material": "PLA",
          "nozzle": "0.4",
          "quantity": 2,
          "status": "pending",
          "unit_price": "12.40",
          "list_unit_price": "12.40",
          "discount_pct": "0",
          "total": "12.40",
          "currency": "EUR",
          "tiers": [
            {
              "min_quantity": null,
              "unit_price": null
            }
          ],
          "estimate": {
            "weight_grams": 31.2,
            "time_seconds": 5400,
            "unit_yield": 1,
            "printer_model": "Bambu Lab A2L",
            "nozzle": "0.4",
            "material": "PLA",
            "infill_pct": 15,
            "wall_loops": 3,
            "support": "auto",
            "warnings": [],
            "priced_at": "2026-10-24T09:30:00Z"
          },
          "failure_code": null,
          "failure_message": null,
          "created_at": "2026-10-24T09:30:00Z",
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl",
          "status": "pending",
          "size_bytes": 1048576,
          "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "rejection_code": null,
          "facts": {
            "triangles": 48210,
            "objects": 1,
            "bbox_mm": [
              60,
              42.5,
              18
            ],
            "producer": "Bambu Studio 02.08.02.61",
            "sanitised": false
          },
          "created_at": "2026-10-24T09:30:00Z",
          "ready_at": "2026-10-24T09:30:00Z",
          "purge_after": "2026-10-24T09:30:00Z"
        }
      }
    }
  ],
  "meta": {
    "next_after": 1234,
    "has_more": false
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/events?after=0&limit=50" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Webhooks

Signed HTTP notifications and their delivery log.

### GET /v1/webhook-endpoints

**List endpoints**

Responses:

- `200` — Endpoints.

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `url` | string | yes |  |
| `description` | string | no |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `event_types` | array of string \| null | yes |  |
| `enabled` | boolean | yes |  |
| `disabled_reason` | string \| null | no |  |
| `consecutive_failures` | integer | no |  |
| `last_success_at` | string (date-time) \| null | no |  |
| `secret_rotated_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "url": "https://shop.example.com/webhooks/layercrew",
      "description": "Production events",
      "environment": "live",
      "event_types": [
        "event types"
      ],
      "enabled": true,
      "disabled_reason": null,
      "consecutive_failures": 0,
      "last_success_at": "2026-10-24T09:30:00Z",
      "secret_rotated_at": "2026-10-24T09:30:00Z",
      "created_at": "2026-10-24T09:30:00Z"
    }
  ]
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/webhook-endpoints" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/webhook-endpoints

**Register an endpoint**

The URL must be HTTPS on port 443 (or 8443) on a public host. The signing `secret` is returned once; store it. Each delivery carries `webhook-id`, `webhook-timestamp` and `webhook-signature` (Standard Webhooks). Your plan caps the number of endpoints per environment.

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string (uri) | yes | (up to 500 characters) |
| `description` | string | no | (up to 200 characters) |
| `event_types` | array of EventType \| null | no | null = every event. |

```json
{
  "url": "https://shop.example.com/webhooks/layercrew",
  "description": "Production events",
  "event_types": [
    "order.received"
  ]
}
```

Responses:

- `201` — Created; `secret` is shown once.
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).
- `422` — A field is wrong; `error.details` maps field names to messages (`VALIDATION_ERROR`, `IDEMPOTENCY_KEY_REQUIRED`, `MATERIAL_UNAVAILABLE`, `FILE_REJECTED`, `ENDPOINT_URL_INVALID`).

Response `201`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `url` | string | yes |  |
| `description` | string | no |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `event_types` | array of string \| null | yes |  |
| `enabled` | boolean | yes |  |
| `disabled_reason` | string \| null | no |  |
| `consecutive_failures` | integer | no |  |
| `last_success_at` | string (date-time) \| null | no |  |
| `secret_rotated_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `secret` | string | yes | `whsec_` + base64. Shown once. |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "url": "https://shop.example.com/webhooks/layercrew",
    "description": "Production events",
    "environment": "live",
    "event_types": [
      "event types"
    ],
    "enabled": true,
    "disabled_reason": null,
    "consecutive_failures": 0,
    "last_success_at": "2026-10-24T09:30:00Z",
    "secret_rotated_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "secret": "secret"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/webhook-endpoints" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://shop.example.com/webhooks/layercrew",
  "description": "Production events",
  "event_types": [
    "order.received"
  ]
}'
```
### GET /v1/webhook-endpoints/{endpoint_id}

**Get an endpoint**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The endpoint.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `url` | string | yes |  |
| `description` | string | no |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `event_types` | array of string \| null | yes |  |
| `enabled` | boolean | yes |  |
| `disabled_reason` | string \| null | no |  |
| `consecutive_failures` | integer | no |  |
| `last_success_at` | string (date-time) \| null | no |  |
| `secret_rotated_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "url": "https://shop.example.com/webhooks/layercrew",
    "description": "Production events",
    "environment": "live",
    "event_types": [
      "event types"
    ],
    "enabled": true,
    "disabled_reason": null,
    "consecutive_failures": 0,
    "last_success_at": "2026-10-24T09:30:00Z",
    "secret_rotated_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/webhook-endpoints/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### PATCH /v1/webhook-endpoints/{endpoint_id}

**Update an endpoint**

Re-enabling a disabled endpoint resets its failure count and re-queues deliveries skipped in the last 7 days, in order.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string (uuid) | yes |  |

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string (uri) | no |  |
| `description` | string | no |  |
| `event_types` | array of EventType \| null | no |  |
| `enabled` | boolean | no |  |

```json
{
  "url": "https://shop.example.com/webhooks/layercrew",
  "description": "Production events",
  "event_types": [
    "order.received"
  ],
  "enabled": true
}
```

Responses:

- `200` — Updated.
- `404` — No such object in this environment (`NOT_FOUND`).
- `422` — A field is wrong; `error.details` maps field names to messages (`VALIDATION_ERROR`, `IDEMPOTENCY_KEY_REQUIRED`, `MATERIAL_UNAVAILABLE`, `FILE_REJECTED`, `ENDPOINT_URL_INVALID`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `url` | string | yes |  |
| `description` | string | no |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `event_types` | array of string \| null | yes |  |
| `enabled` | boolean | yes |  |
| `disabled_reason` | string \| null | no |  |
| `consecutive_failures` | integer | no |  |
| `last_success_at` | string (date-time) \| null | no |  |
| `secret_rotated_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "url": "https://shop.example.com/webhooks/layercrew",
    "description": "Production events",
    "environment": "live",
    "event_types": [
      "event types"
    ],
    "enabled": true,
    "disabled_reason": null,
    "consecutive_failures": 0,
    "last_success_at": "2026-10-24T09:30:00Z",
    "secret_rotated_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X PATCH "https://engine.layercrew.com/v1/webhook-endpoints/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://shop.example.com/webhooks/layercrew",
  "description": "Production events",
  "event_types": [
    "order.received"
  ],
  "enabled": true
}'
```
### DELETE /v1/webhook-endpoints/{endpoint_id}

**Delete an endpoint**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string (uuid) | yes |  |

Responses:

- `204` — Deleted.
- `404` — No such object in this environment (`NOT_FOUND`).

```bash
curl -X DELETE "https://engine.layercrew.com/v1/webhook-endpoints/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/webhook-endpoints/{endpoint_id}/rotate-secret

**Rotate the signing secret**

Returns a new secret once. For 24 hours every delivery is signed with both the new and the old secret, so you can switch without a gap.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string (uuid) | yes |  |

Responses:

- `200` — Rotated.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `url` | string | yes |  |
| `description` | string | no |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `event_types` | array of string \| null | yes |  |
| `enabled` | boolean | yes |  |
| `disabled_reason` | string \| null | no |  |
| `consecutive_failures` | integer | no |  |
| `last_success_at` | string (date-time) \| null | no |  |
| `secret_rotated_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `secret` | string | yes | `whsec_` + base64. Shown once. |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "url": "https://shop.example.com/webhooks/layercrew",
    "description": "Production events",
    "environment": "live",
    "event_types": [
      "event types"
    ],
    "enabled": true,
    "disabled_reason": null,
    "consecutive_failures": 0,
    "last_success_at": "2026-10-24T09:30:00Z",
    "secret_rotated_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "secret": "secret"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/webhook-endpoints/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/rotate-secret" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/webhook-endpoints/{endpoint_id}/ping

**Send a signed test event**

Queues a `test.ping` delivery so you can verify your signature check before going live.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_id` | path | string (uuid) | yes |  |

Responses:

- `202` — Queued.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `202`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `seq` | integer | yes |  |
| `status` | DeliveryStatus: `pending`, `in_flight`, `delivered`, `failed`, `skipped` | yes |  |
| `attempts` | integer | yes |  |
| `max_attempts` | integer | no |  |
| `next_attempt_at` | string (date-time) \| null | no |  |
| `last_status_code` | integer \| null | no |  |
| `last_error` | string \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "endpoint_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_type": "event type",
    "seq": 1234,
    "status": "pending",
    "attempts": 1,
    "max_attempts": 12,
    "next_attempt_at": "2026-10-24T09:30:00Z",
    "last_status_code": 200,
    "last_error": null,
    "delivered_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/webhook-endpoints/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/ping" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/webhook-deliveries

**Delivery log**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no |  |
| `per_page` | query | integer | no |  |
| `endpoint_id` | query | string (uuid) | no |  |
| `status` | query | DeliveryStatus | no |  |
| `event_id` | query | string (uuid) | no |  |

Responses:

- `200` — Deliveries, newest first.

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `seq` | integer | yes |  |
| `status` | DeliveryStatus: `pending`, `in_flight`, `delivered`, `failed`, `skipped` | yes |  |
| `attempts` | integer | yes |  |
| `max_attempts` | integer | no |  |
| `next_attempt_at` | string (date-time) \| null | no |  |
| `last_status_code` | integer \| null | no |  |
| `last_error` | string \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": [
    {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "endpoint_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "event_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "event_type": "event type",
      "seq": 1234,
      "status": "pending",
      "attempts": 1,
      "max_attempts": 12,
      "next_attempt_at": "2026-10-24T09:30:00Z",
      "last_status_code": 200,
      "last_error": null,
      "delivered_at": "2026-10-24T09:30:00Z",
      "created_at": "2026-10-24T09:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/webhook-deliveries?page=1&per_page=25" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/webhook-deliveries/{delivery_id}

**A delivery with every attempt**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `delivery_id` | path | string (uuid) | yes |  |

Responses:

- `200` — The delivery.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `seq` | integer | yes |  |
| `status` | DeliveryStatus: `pending`, `in_flight`, `delivered`, `failed`, `skipped` | yes |  |
| `attempts` | integer | yes |  |
| `max_attempts` | integer | no |  |
| `next_attempt_at` | string (date-time) \| null | no |  |
| `last_status_code` | integer \| null | no |  |
| `last_error` | string \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `attempts_log` | array of WebhookAttempt | yes |  |
| `event` | Event | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "endpoint_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_type": "event type",
    "seq": 1234,
    "status": "pending",
    "attempts": 1,
    "max_attempts": 12,
    "next_attempt_at": "2026-10-24T09:30:00Z",
    "last_status_code": 200,
    "last_error": null,
    "delivered_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "attempts_log": [
      {
        "attempt": 1,
        "at": "2026-10-24T09:30:00Z",
        "request_id": "01J9Z8Y7X6W5V4U3T2S1R0Q9P8",
        "status_code": 200,
        "duration_ms": 42,
        "error": "error",
        "response_snippet": "ok"
      }
    ],
    "event": {
      "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "type": "order.received",
      "api_version": "2026-10-24",
      "created_at": "2026-10-24T09:30:00Z",
      "seq": 1234,
      "environment": "live",
      "reason_code": null,
      "data": {
        "order": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "number": "PA-2026-0007",
          "reference": "SHOP-10231",
          "environment": "live",
          "status": "received",
          "pricing_status": "pending",
          "items": [
            {
              "id": null,
              "position": null,
              "file": null,
              "quantity": null,
              "material": null,
              "colour": null,
              "nozzle": null,
              "notes": null,
              "pricing_status": null,
              "list_unit_price": null,
              "discount_pct": null,
              "unit_price": null,
              "line_total": null,
              "estimate": null,
              "failure_code": null,
              "failure_message": null
            }
          ],
          "pricing": {
            "currency": "EUR",
            "subtotal": "12.40",
            "discount_total": "12.40",
            "shipping_total": "12.40",
            "tax_total": "12.40",
            "tax_treatment": "standard",
            "grand_total": "12.40",
            "final": true
          },
          "payment": {
            "method": "bank_transfer",
            "status": "unpaid",
            "ship_before_payment": false,
            "amount_due": "12.40",
            "instructions": {
              "beneficiary": null,
              "iban": null,
              "bank": null,
              "reference": null
            },
            "paid_at": "2026-10-24T09:30:00Z"
          },
          "delivery": {
            "name": "Claire Martin",
            "company": "Atelier Martin",
            "phone": "+33 6 12 34 56 78",
            "email": "claire@example.com",
            "line1": "12 rue de la Paix",
            "line2": "Bâtiment B",
            "city": "Lyon",
            "region": "Auvergne-Rhône-Alpes",
            "postal_code": "69001",
            "country_code": "FR"
          },
          "white_label": {
            "enabled": true,
            "sender_name": "Studio NS",
            "card_text": "Thank you for your order — Studio NS",
            "packaging_preset": "neutral",
            "no_invoice_in_box": true,
            "language": "fr"
          },
          "metadata": {
            "shop_order_id": "10231"
          },
          "notes": "Fit check before the batch, please.",
          "shipments": [
            {
              "carrier": null,
              "tracking_number": null,
              "tracking_url": null,
              "status": null,
              "last_tracking_status": null,
              "shipped_at": null,
              "delivered_at": null
            }
          ],
          "invoices": [
            {
              "id": null,
              "number": null,
              "kind": null,
              "issued_at": null
            }
          ],
          "rejection": null,
          "hold": null,
          "created_at": "2026-10-24T09:30:00Z",
          "updated_at": "2026-10-24T09:30:00Z",
          "accepted_at": "2026-10-24T09:30:00Z",
          "shipped_at": "2026-10-24T09:30:00Z",
          "delivered_at": "2026-10-24T09:30:00Z",
          "cancelled_at": "2026-10-24T09:30:00Z",
          "links": {
            "self": "self",
            "events": "events"
          }
        },
        "quote": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "file_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "material": "PLA",
          "nozzle": "0.4",
          "quantity": 2,
          "status": "pending",
          "unit_price": "12.40",
          "list_unit_price": "12.40",
          "discount_pct": "0",
          "total": "12.40",
          "currency": "EUR",
          "tiers": [
            {
              "min_quantity": null,
              "unit_price": null
            }
          ],
          "estimate": {
            "weight_grams": 31.2,
            "time_seconds": 5400,
            "unit_yield": 1,
            "printer_model": "Bambu Lab A2L",
            "nozzle": "0.4",
            "material": "PLA",
            "infill_pct": 15,
            "wall_loops": 3,
            "support": "auto",
            "warnings": [],
            "priced_at": "2026-10-24T09:30:00Z"
          },
          "failure_code": null,
          "failure_message": null,
          "created_at": "2026-10-24T09:30:00Z",
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl",
          "status": "pending",
          "size_bytes": 1048576,
          "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "rejection_code": null,
          "facts": {
            "triangles": 48210,
            "objects": 1,
            "bbox_mm": [
              60,
              42.5,
              18
            ],
            "producer": "Bambu Studio 02.08.02.61",
            "sanitised": false
          },
          "created_at": "2026-10-24T09:30:00Z",
          "ready_at": "2026-10-24T09:30:00Z",
          "purge_after": "2026-10-24T09:30:00Z"
        }
      }
    }
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/webhook-deliveries/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### POST /v1/webhook-deliveries/{delivery_id}/redeliver

**Send a delivery again**

Allowed for delivered, failed and skipped deliveries; a delivery in flight answers 409. The exact original bytes are re-sent with a fresh signature and timestamp.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `delivery_id` | path | string (uuid) | yes |  |

Responses:

- `202` — Queued.
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).

Response `202`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `seq` | integer | yes |  |
| `status` | DeliveryStatus: `pending`, `in_flight`, `delivered`, `failed`, `skipped` | yes |  |
| `attempts` | integer | yes |  |
| `max_attempts` | integer | no |  |
| `next_attempt_at` | string (date-time) \| null | no |  |
| `last_status_code` | integer \| null | no |  |
| `last_error` | string \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "endpoint_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "event_type": "event type",
    "seq": 1234,
    "status": "pending",
    "attempts": 1,
    "max_attempts": 12,
    "next_attempt_at": "2026-10-24T09:30:00Z",
    "last_status_code": 200,
    "last_error": null,
    "delivered_at": "2026-10-24T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/webhook-deliveries/4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/redeliver" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Requests

Your own request log, for debugging.

### GET /v1/requests

**Your request log**

Every call made with your keys in the last 90 days: when, which route, the status, the duration and the request id to quote to support. Bodies are never stored.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no |  |
| `per_page` | query | integer | no |  |
| `from` | query | string (date-time) | no |  |
| `to` | query | string (date-time) | no |  |
| `status` | query | integer | no |  |
| `route` | query | string | no |  |

Responses:

- `200` — Requests, newest first.

Response `200` (each item of `data`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `client_request_id` | string \| null | no |  |
| `key_id` | string | no |  |
| `method` | string | yes |  |
| `route` | string | yes |  |
| `path` | string | no |  |
| `status` | integer | yes |  |
| `error_code` | string \| null | no |  |
| `duration_ms` | integer | yes |  |
| `resource_ref` | string \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": [
    {
      "request_id": "01J9Z8Y7X6W5V4U3T2S1R0Q9P8",
      "client_request_id": null,
      "key_id": "k7m2p9q4r1s8t3u6",
      "method": "method",
      "route": "POST /v1/orders",
      "path": "/v1/orders",
      "status": 1,
      "error_code": null,
      "duration_ms": 42,
      "resource_ref": "order:5d0c…",
      "created_at": "2026-10-24T09:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/requests?page=1&per_page=25" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```
### GET /v1/requests/{request_id}

**One request by its id**

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `request_id` | path | string | yes |  |

Responses:

- `200` — The request.
- `404` — No such object in this environment (`NOT_FOUND`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `client_request_id` | string \| null | no |  |
| `key_id` | string | no |  |
| `method` | string | yes |  |
| `route` | string | yes |  |
| `path` | string | no |  |
| `status` | integer | yes |  |
| `error_code` | string \| null | no |  |
| `duration_ms` | integer | yes |  |
| `resource_ref` | string \| null | no |  |
| `created_at` | string (date-time) | yes |  |

```json
{
  "data": {
    "request_id": "01J9Z8Y7X6W5V4U3T2S1R0Q9P8",
    "client_request_id": null,
    "key_id": "k7m2p9q4r1s8t3u6",
    "method": "method",
    "route": "POST /v1/orders",
    "path": "/v1/orders",
    "status": 1,
    "error_code": null,
    "duration_ms": 42,
    "resource_ref": "order:5d0c…",
    "created_at": "2026-10-24T09:30:00Z"
  }
}
```

```bash
curl -X GET "https://engine.layercrew.com/v1/requests/01J9Z8Y7X6W5V4U3T2S1R0Q9P8" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## Test mode

Available with test keys only.

### POST /v1/test/orders/{order_id}/advance

**Move a test order to its next state**

Test keys only. Emits the same events and webhooks a live order would. Allowed moves: `pending_review` → `accepted` or `rejected`; `accepted` → `in_production` → `ready_to_ship` → `shipped` → `delivered`; any non-terminal state ↔ `on_hold`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `order_id` | path | string | yes | The order id (UUID) or number (`PA-YYYY-NNNN`). |

Request body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `reason` | string | no | (up to 500 characters) |

```json
{
  "to": "received",
  "reason": "Cancelled by the customer before production."
}
```

Responses:

- `200` — Advanced.
- `403` — Use a test key for this route (`TEST_ONLY`).
- `404` — No such object in this environment (`NOT_FOUND`).
- `409` — The request conflicts with the current state; `error.code` says how (`REFERENCE_TAKEN`, `FILE_IN_USE`, `FILE_NOT_READY`, `CANNOT_CANCEL`, `ENDPOINT_LIMIT`, `DELIVERY_IN_FLIGHT`, `IDEMPOTENCY_KEY_REUSED`, `IDEMPOTENCY_IN_PROGRESS`, `TERMS_REACCEPTANCE_REQUIRED`).

Response `200`:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `number` | string | yes |  |
| `reference` | string | yes |  |
| `environment` | Environment: `live`, `test` | yes |  |
| `status` | OrderStatus: `received`, `pending_review`, `accepting`, `accepted`, `in_production`, `ready_to_ship`, `shipped`, `delivered`, `on_hold`, `cancelled`, `rejected` | yes | received: pricing in progress · pending_review: priced, a LayerCrew engineer is looking at it · accepted: in the production queue · in_production · ready_to_ship: passed quality check and packed · shipped: handed to the courier · delivered (final) · on_hold: we need something, see `hold` · cancelled · rejected, see `rejection`. |
| `pricing_status` | enum: `pending`, `priced`, `failed` | yes |  |
| `items` | array of OrderItem | yes |  |
| `pricing` | Pricing | yes |  |
| `payment` | Payment | no |  |
| `delivery` | Delivery | yes |  |
| `white_label` | WhiteLabel | yes | White label is on by default: neutral packaging, no LayerCrew marks on parts, packaging or inserts, no invoice in the box. The courier label names LayerCrew as the shipping account (a courier requirement) and shows your sender name as the contact line; packaging and inserts carry your branding settings. |
| `metadata` | object | yes |  |
| `notes` | string | no |  |
| `shipments` | array of Shipment | yes |  |
| `invoices` | array of Invoice | yes |  |
| `rejection` | Rejection \| null | no |  |
| `hold` | Hold \| null | no |  |
| `created_at` | string (date-time) | yes |  |
| `updated_at` | string (date-time) | yes |  |
| `accepted_at` | string (date-time) \| null | no |  |
| `shipped_at` | string (date-time) \| null | no |  |
| `delivered_at` | string (date-time) \| null | no |  |
| `cancelled_at` | string (date-time) \| null | no |  |
| `links` | object | yes |  |

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "number": "PA-2026-0007",
    "reference": "SHOP-10231",
    "environment": "live",
    "status": "received",
    "pricing_status": "pending",
    "items": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "position": 1,
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl"
        },
        "quantity": 2,
        "material": "PLA",
        "colour": "black",
        "nozzle": "0.4",
        "notes": "Fit check before the batch, please.",
        "pricing_status": "pending",
        "list_unit_price": "12.40",
        "discount_pct": "0",
        "unit_price": "12.40",
        "line_total": "12.40",
        "estimate": {
          "weight_grams": 31.2,
          "time_seconds": 5400,
          "unit_yield": 1,
          "printer_model": "Bambu Lab A2L",
          "nozzle": "0.4",
          "material": "PLA",
          "infill_pct": 15,
          "wall_loops": 3,
          "support": "auto",
          "warnings": [
            "warnings"
          ],
          "priced_at": "2026-10-24T09:30:00Z"
        },
        "failure_code": null,
        "failure_message": null
      }
    ],
    "pricing": {
      "currency": "EUR",
      "subtotal": "12.40",
      "discount_total": "12.40",
      "shipping_total": "12.40",
      "tax_total": "12.40",
      "tax_treatment": "standard",
      "grand_total": "12.40",
      "final": true
    },
    "payment": {
      "method": "bank_transfer",
      "status": "unpaid",
      "ship_before_payment": false,
      "amount_due": "12.40",
      "instructions": {
        "beneficiary": "LAYER CREW SRL",
        "iban": "RO49AAAA1B31007593840000",
        "bank": "Example Bank",
        "reference": "SHOP-10231"
      },
      "paid_at": "2026-10-24T09:30:00Z"
    },
    "delivery": {
      "name": "Claire Martin",
      "company": "Atelier Martin",
      "phone": "+33 6 12 34 56 78",
      "email": "claire@example.com",
      "line1": "12 rue de la Paix",
      "line2": "Bâtiment B",
      "city": "Lyon",
      "region": "Auvergne-Rhône-Alpes",
      "postal_code": "69001",
      "country_code": "FR"
    },
    "white_label": {
      "enabled": true,
      "sender_name": "Studio NS",
      "card_text": "Thank you for your order — Studio NS",
      "packaging_preset": "neutral",
      "no_invoice_in_box": true,
      "language": "fr"
    },
    "metadata": {
      "shop_order_id": "10231"
    },
    "notes": "Fit check before the batch, please.",
    "shipments": [
      {
        "carrier": "FAN Courier",
        "tracking_number": "2057100012345",
        "tracking_url": "https://www.fancourier.ro/awb-tracking/?awb=2057100012345",
        "status": "shipped",
        "last_tracking_status": "last tracking status",
        "shipped_at": "2026-10-24T09:30:00Z",
        "delivered_at": "2026-10-24T09:30:00Z"
      }
    ],
    "invoices": [
      {
        "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "number": "number",
        "kind": "fiscal",
        "issued_at": "2026-10-24T09:30:00Z"
      }
    ],
    "rejection": null,
    "hold": null,
    "created_at": "2026-10-24T09:30:00Z",
    "updated_at": "2026-10-24T09:30:00Z",
    "accepted_at": "2026-10-24T09:30:00Z",
    "shipped_at": "2026-10-24T09:30:00Z",
    "delivered_at": "2026-10-24T09:30:00Z",
    "cancelled_at": "2026-10-24T09:30:00Z",
    "links": {
      "self": "self",
      "events": "events"
    }
  }
}
```

```bash
curl -X POST "https://engine.layercrew.com/v1/test/orders/PA-2026-0007/advance" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "received",
  "reason": "Cancelled by the customer before production."
}'
```
