An order is one delivery to one of your customers: one or more items — each a ready file in a material at a quantity — plus your own reference, the delivery address, white-label instructions and any metadata you want back. You create it with one call; we price it, review it, accept it, produce it, ship it and tell you about every step.
Create an order
curl -X POST "https://engine.layercrew.com/v1/orders" \
-H "Authorization: Bearer $LAYERCREW_API_KEY" \
-H "Idempotency-Key: 2f5c1c2e-0f6a-4d2b-9a1e-7b3c4d5e6f70" \
-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": "Visible face is the flat side."
}
],
"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",
"postal_code": "69001",
"country_code": "FR"
},
"white_label": {
"sender_name": "Studio NS",
"card_text": "Merci Claire — Studio NS",
"packaging_preset": "neutral",
"no_invoice_in_box": true,
"language": "fr"
},
"notes": "Fit check before the batch, please.",
"metadata": {
"shop_order_id": "10231",
"customer_id": "c_8812"
}
}'The answer is 202 with the order in received and pricing_status: pending. Within minutes every item is priced and order.priced follows with the order in pending_review. The Idempotency-Key header is required: see Idempotency.
The body
| Field | Required | Rules |
|---|---|---|
reference | yes | Your own identifier, up to 120 characters, unique among your orders in this environment. |
items[] | yes | One to your plan's max_items_per_order (max_test_items_per_order in test). |
items[].file_id | yes | A ready file of yours in the same environment. |
items[].quantity | yes | 1 to your plan's max_quantity_per_item. |
items[].material | yes | A material from GET /v1/materials. |
items[].colour | no | A colour id of that material. Left out, the engineer picks at production. |
items[].nozzle | no | From the material's nozzles; default 0.4. |
items[].notes | no | Up to 500 characters, read by the engineer. |
delivery | yes | Your customer's address — below. |
white_label | no | Sender name, card text, packaging — below. White label is on even when the block is left out. |
notes | no | Up to 2000 characters, read by the engineer. |
metadata | no | Up to 20 keys with string values up to 200 characters; returned untouched everywhere. |
Reference
reference is your identifier for the order — a shop order number, a job id. It must be unique among your orders in the environment: a duplicate answers 409 REFERENCE_TAKEN and error.details.order_id names the existing order, so even a retry without its Idempotency-Key finds its order. Look orders up by it with GET /v1/orders?reference=SHOP-10231.
Items
file_idmust be areadyfile of yours in the same environment —409 FILE_NOT_READYotherwise.material, and the optionalcolourid, come fromGET /v1/materialsand your plan —422 MATERIAL_UNAVAILABLEotherwise.- A 3MF with several objects is priced as the plate it holds;
estimate.unit_yieldsays how many parts one plate makes. - Every string passes a cleaning step that drops control characters and markup before it is stored.
Delivery
Your customer's address as the courier needs it: name, phone (the courier requires it), line1, city, postal_code and country_code (ISO 3166-1 alpha-2) are required; company, email, line2 and region are optional outside Romania.
Delivery fields are personal data of your customer. We process them under the data processing agreement, show them to production and shipping staff only, redact them after your plan's recipient_retention_days (90 days on the default plan) and at once on request: DELETE /v1/orders/{id}/recipient on a finished order keeps city, postal code and country for fiscal records and replaces the rest with [redacted], on the order and on its stored events. order.recipient_redacted confirms it.
White label
White label is on by default. Every order ships in neutral packaging with no LayerCrew marks on the parts, the packaging or the inserts, and no invoice in the box.
| Field | Default | Meaning |
|---|---|---|
enabled | true | Set false only if you want our standard packaging. |
sender_name | your company name | The contact line on the courier label, up to 80 characters. |
card_text | none | Printed on a card in the parcel, up to 500 characters; can differ on every order. |
packaging_preset | neutral | neutral, or partner_supplied when you send us your packaging. |
no_invoice_in_box | true | The invoice goes to you, never into the parcel. |
language | your account's locale | The language of any printed insert, as a two-letter code. |
Metadata and notes
metadata is yours: up to 20 keys with string values up to 200 characters, returned untouched on the order and in every webhook — a shop order id, a customer id, a campaign. notes (up to 2000 characters) and items[].notes are read by the engineer who reviews the order.
Pricing
pricing carries currency (EUR), subtotal, discount_total, shipping_total, tax_total, tax_treatment and grand_total, each a string with two decimals. Per item: list_unit_price, discount_pct, unit_price, line_total and the slicer's estimate. The block is provisional at order.priced (final: false) and frozen at acceptance (final: true); once frozen it is the price you pay.
tax_treatment | When |
|---|---|
standard | Romanian VAT applies — a Romanian company, or a company without a valid EU VAT number. |
reverse_charge | An EU company with a valid VAT number: no VAT on the invoice, you account for it. |
export | Delivery outside the EU. |
exempt | An exemption agreed in writing. |
test | A test order; nothing is invoiced. |
{
"pricing": {
"currency": "EUR",
"subtotal": "24.80",
"discount_total": "0.00",
"shipping_total": "14.99",
"tax_total": "0.00",
"tax_treatment": "reverse_charge",
"grand_total": "39.79",
"final": true
},
"payment": {
"method": "bank_transfer",
"status": "unpaid",
"ship_before_payment": false,
"amount_due": "39.79",
"instructions": {
"beneficiary": "LAYER CREW SRL",
"iban": "RO49AAAA1B31007593840000",
"bank": "Example Bank",
"reference": "PA-2026-0007"
},
"paid_at": null
}
}pricing and payment on an accepted order
Payment
Payment is by bank transfer against the order. payment.instructions appear at acceptance — beneficiary, iban, bank and the reference to put on the transfer, which is the order number. On the default plan production starts once the transfer arrives and order.paid is emitted; ship_before_payment: true on a plan or an agreed override starts production at acceptance instead. The invoice follows as order.invoiced: GET /v1/orders/{id}/invoices lists it and GET /v1/orders/{id}/invoices/{invoice_id}/download-url returns a 15-minute link to the PDF. Card payment is a later addition; a plan with payment terms is agreed in writing.
Acceptance and rejection
Every order is read by an engineer before it is accepted, usually within one business day — the same design-for-manufacturing look every file gets. An accepted order carries accepted_at, a frozen pricing block and payment.instructions. A declined order is rejected with rejection.code, rejection.message (the fact and the next step) and rejection.note (what the engineer wrote for you).
rejection.code | What it means | Next step |
|---|---|---|
needs_split | The part is laid out larger than our print bed. | Split it into sub-assemblies, or ask us to take it by hand. |
design_change_needed | A feature will not print as drawn; the note says which. | Change the design and order again. |
material_unavailable | The material is out of stock for this run. | Pick another material, or wait for stock. |
out_of_capacity | The queue is full for the date you need. | Order again with a later date in mind. |
address_unserviceable | The courier has no route to the address as given. | Correct the address and order again. |
price_review | The order needs a price by hand. | We write to you with the number. |
other | See the note. | The note says what to do. |
Holds
When we need something from you — a question about a feature, a colour that ran out — the order goes on_hold with hold.reason and hold.since and order.on_hold is emitted. order.released brings it back to the state it had before. Holds are possible from received through ready_to_ship.
Cancelling
POST /v1/orders/{id}/cancel with an optional reason is allowed while the order is received, pending_review or accepted — before production starts. Once printing has started the call answers 409 CANNOT_CANCEL; write to us from the order page in your dashboard and we tell you what is still possible. A cancelled order is final and is never invoiced.
Reading orders
GET /v1/orders/{id}takes the id or the number (PA-2026-0007).GET /v1/orders?status=&reference=&created_after=&page=&per_page=lists newest first, 25 a page and up to 100.GET /v1/orders/{id}/eventsreturns the order's events inseqorder;GET /v1/eventsthe whole account's. See Lifecycle and events.links.selfandlinks.eventson every order point at both.
Partner orders are yours alone: they are invisible on the customer-facing routes and on the customer dashboard, and visible under Integrations → Orders in yours. Every endpoint is in the reference.