# Quickstart

> From company sign-up to a test order that reports to your webhook, in ten steps — then the one change that takes you live.

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

This walk-through takes you from a company account to a test order that reports its progress to your webhook. Everything happens in the test environment; the last step is the switch to live.

> **Conventions.** Base URL `https://engine.layercrew.com/v1`. Every response is JSON in an envelope: `{"data": …}` for one object, `{"data": [...], "meta": {…}}` for lists, `{"error": {code, message, details?}}` on failure. Money is a string with two decimals in EUR; timestamps are RFC 3339 in UTC; identifiers are UUIDs.

## 1. Register your company

Sign up at [/developers/register](https://layercrew.com/developers/register) with your company's name, legal identifier or VAT number, country and a contact. Partners are companies, so the form has no private-individual option. A Romanian company is checked against ANAF; an EU company against the VAT register when it answers.

## 2. Accept the agreements

In your dashboard under Integrations → Agreement, read and accept the integration agreement and the data processing agreement together. Acceptance is recorded — who, when, from which address, which version — and the PDF is emailed to you. LayerCrew countersigns in AdminCrew and the countersigned PDF goes to both sides; `agreement.countersigned` appears in your event feed.

## 3. Create a test key

Under Integrations → Keys, create a test key. It looks like `lc_test_<16 characters>_<64 hex characters>` and is shown once: store it in your secret manager. Check it:

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

_200 — the key, its environment and its plan_

```json
{
  "data": {
    "partner": {
      "id": "0d2f6a1c-3b4e-4d5f-8a9b-1c2d3e4f5a6b",
      "number": "LC-PT-0007",
      "name": "Studio NS"
    },
    "environment": "test",
    "key_id": "k7m2p9q4r1s8t3u6",
    "plan": {
      "key": "default",
      "name": "Default"
    },
    "rate_limit": {
      "per_second": 2,
      "burst": 10
    },
    "server_time": "2026-10-24T09:30:00Z"
  }
}
```

## 4. Upload a file

Uploading is two calls and one PUT. First declare the file with its size in bytes and the lowercase hex SHA-256 of the exact bytes you will send:

```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"
  }'
```

_The same declaration with the hash and size computed from the file_

```bash
FILE=bracket-v3.stl
SIZE=$(stat -c %s "$FILE")                    # macOS: stat -f %z
HASH=$(sha256sum "$FILE" | cut -d' ' -f1)      # macOS: shasum -a 256
curl -X POST "https://engine.layercrew.com/v1/files" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\":\"$FILE\",\"size_bytes\":$SIZE,\"sha256\":\"$HASH\"}"
```

_201 — the file and a one-time upload slot_

```json
{
  "data": {
    "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "filename": "bracket-v3.stl",
    "format": "stl",
    "status": "pending",
    "size_bytes": 1048576,
    "created_at": "2026-10-24T09:31:00Z",
    "upload": {
      "method": "PUT",
      "url": "https://storage.googleapis.com/…signed…",
      "headers": {
        "Content-Type": "application/octet-stream",
        "x-goog-content-length-range": "0,1048576"
      },
      "expires_at": "2026-10-24T09:46:00Z"
    }
  }
}
```

PUT the raw bytes to `upload.url` before `expires_at`, sending exactly the headers in `upload.headers`. **Both headers are mandatory** — `Content-Type` and `x-goog-content-length-range` — because the signature covers them; a PUT missing either is refused by the storage service.

```bash
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/octet-stream" \
  -H "x-goog-content-length-range: 0,1048576" \
  --data-binary @bracket-v3.stl
```

## 5. Complete the upload

Tell us the bytes are there. We check the object's size and hash against what you declared, read the model, strip any embedded slicer scripts from a 3MF and store the canonical copy:

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

_200 — ready to quote and order_

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

`status` is `ready`, or `rejected` with a `rejection_code` you can act on — see [Files](https://layercrew.com/developers/files#rejections). `complete` is idempotent: call it again and you get the same file back.

## 6. Ask for a price (optional)

```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", "quantity": 2 }'
```

The answer is `202` with the quote in `pending`. Pricing slices the file for real and takes seconds to a few minutes; poll `GET /v1/quotes/{id}` until `status` is `priced`, or listen for `quote.priced`. See [Quotes](https://layercrew.com/developers/quotes).

## 7. Create an order

An order needs your own `reference` (unique in this environment), the items, your customer's delivery address and an `Idempotency-Key` header:

```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"
    }
  ],
  "delivery": {
    "name": "Claire Martin",
    "phone": "+33 6 12 34 56 78",
    "email": "claire@example.com",
    "line1": "12 rue de la Paix",
    "city": "Lyon",
    "postal_code": "69001",
    "country_code": "FR"
  },
  "white_label": {
    "sender_name": "Studio NS",
    "card_text": "Merci Claire — Studio NS",
    "language": "fr"
  },
  "metadata": {
    "shop_order_id": "10231"
  }
}'
```

_202 — received; pricing has started_

```json
{
  "data": {
    "id": "7a3e9c10-2b4d-4f61-9e8a-5c6d7e8f9a0b",
    "number": "PA-2026-0007",
    "reference": "SHOP-10231",
    "environment": "test",
    "status": "received",
    "pricing_status": "pending",
    "items": [
      {
        "id": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9",
        "position": 1,
        "file": {
          "id": "4f1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "filename": "bracket-v3.stl",
          "format": "stl"
        },
        "quantity": 2,
        "material": "PLA",
        "colour": "black",
        "nozzle": "0.4",
        "pricing_status": "pending"
      }
    ],
    "pricing": {
      "currency": "EUR",
      "final": false
    },
    "delivery": {
      "name": "Claire Martin",
      "city": "Lyon",
      "postal_code": "69001",
      "country_code": "FR",
      "line1": "12 rue de la Paix",
      "phone": "+33 6 12 34 56 78",
      "email": "claire@example.com"
    },
    "white_label": {
      "enabled": true,
      "sender_name": "Studio NS",
      "card_text": "Merci Claire — Studio NS",
      "no_invoice_in_box": true,
      "language": "fr"
    },
    "metadata": {
      "shop_order_id": "10231"
    },
    "shipments": [],
    "invoices": [],
    "rejection": null,
    "hold": null,
    "created_at": "2026-10-24T09:35:00Z",
    "updated_at": "2026-10-24T09:35:00Z",
    "links": {
      "self": "/v1/orders/7a3e9c10-2b4d-4f61-9e8a-5c6d7e8f9a0b",
      "events": "/v1/orders/7a3e9c10-2b4d-4f61-9e8a-5c6d7e8f9a0b/events"
    }
  }
}
```

A retry with the same key and the same body returns the first answer with `Idempotent-Replayed: true`; the same key with a different body answers `409 IDEMPOTENCY_KEY_REUSED`. A second order with the same `reference` answers `409 REFERENCE_TAKEN` and names the existing one. See [Orders](https://layercrew.com/developers/orders) and [Idempotency](https://layercrew.com/developers/idempotency).

## 8. Follow the order

Every change is an event with a per-partner `seq`. Poll the account feed from the last `seq` you processed; `meta.next_after` and `meta.has_more` drive the loop:

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

A live order moves as production happens. In the test environment you move it yourself, and each move emits the same event a live order would:

```bash
curl -X POST "https://engine.layercrew.com/v1/test/orders/7a3e9c10-2b4d-4f61-9e8a-5c6d7e8f9a0b/advance" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "accepted" }'
```

## 9. Receive webhooks

```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" }'
```

_201 — the secret is shown once_

```json
{
  "data": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "url": "https://shop.example.com/webhooks/layercrew",
    "description": "Production events",
    "environment": "test",
    "event_types": null,
    "enabled": true,
    "consecutive_failures": 0,
    "created_at": "2026-10-24T09:40:00Z",
    "secret": "whsec_c2VjcmV0LWJ5dGVzLWZvci1kb2NzLW9ubHk="
  }
}
```

Store the `secret`. Send yourself a signed test event and verify it with the snippet for your language on the [Webhooks](https://layercrew.com/developers/webhooks#verifying) page:

```bash
curl -X POST "https://engine.layercrew.com/v1/webhook-endpoints/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/ping" \
  -H "Authorization: Bearer $LAYERCREW_API_KEY"
```

## 10. Go live

1. Wait for `agreement.countersigned`, or check `GET /v1/account`: both agreements show `countersigned`.
2. Create a live key under Integrations → Keys (the account owner does this) and store it next to the test one.
3. Register your production webhook endpoint with the live key — endpoints, files, quotes and orders are per environment.
4. Switch the key in your configuration. Nothing else changes: same routes, same bodies, same events.

> **Pace.** Keep to two requests a second with small bursts — the default plan; your plan may allow more, and `GET /v1/ping` tells you. A `403` without a JSON body means the network edge throttled your address: back off 90 seconds. Never retry an order creation blindly — send the same `Idempotency-Key` again, or look the order up by `reference`. See [Rate limits](https://layercrew.com/developers/rate-limits).
