Quickstart: your first order in 10 minutes
This page takes you from zero to a personalized order inside Meykt using nothing but curl. The base URL of the API is https://api.meykt.com/v1; every request is authenticated with an API key in the Authorization header. Start in the test environment — test orders are visible in the order list but never reach production.
#Step 1: Create a test key
In the Meykt dashboard go to Integrations → API access → "Create key".
- Choose the environment Test. Test keys start with
mk_test_, live keys withmk_live_. - Select the scopes you need. For this quickstart:
orders:write(send orders),orders:read(read them back),files:write(upload files). There is no scope hierarchy —orders:writedoes not includeorders:read. - Copy the key immediately. The plaintext key is shown exactly once. If you lose it, create a new key and revoke the old one.
Keep the key out of your source code and out of URLs — pass it only as a header.
export MEYKT_API_KEY="mk_test_a1b2c3d4e5f6g7h8i9j0k1l2"#Step 2: Verify the connection
GET /v1/ping confirms which key and which environment you are actually using. It requires no scope.
curl https://api.meykt.com/v1/ping \
-H "Authorization: Bearer $MEYKT_API_KEY"Response 200:
{
"data": {
"organization": { "name": "Musterwerkstatt" },
"environment": "test",
"scopes": ["orders:read", "orders:write", "files:write"],
"key_prefix": "mk_test_a1b2c3",
"api_version": "v1"
}
}Every authenticated response carries the rate limit headers X-RateLimit-Limit: 120, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). The limit is 120 requests per minute per key.
If you get 401 with the code invalid_api_key, the header is malformed or the key is unknown, revoked or expired — all four cases return the same code on purpose. See /developers/errors/invalid_api_key.
#Step 3: Send your first order
POST /v1/orders creates an order. external_order_id is your own order number and is the key for duplicate protection.
curl -X POST https://api.meykt.com/v1/orders \
-H "Authorization: Bearer $MEYKT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_order_id": "ORDER-2026-1044",
"customer": { "name": "Anna Beispiel" },
"items": [{
"sku": "BECHER-GROSS",
"name": "Becher gross",
"quantity": 2,
"personalization": { "gravur": "Alles Gute", "name": "Anna" }
}]
}'Response 201:
{
"data": {
"order": {
"id": "8f2c1d0a-6b44-4c11-9a3e-1f0c9d7a5b21",
"external_order_id": "ORDER-2026-1044",
"order_number": "ORDER-2026-1044",
"status": "pending",
"is_test": true,
"currency": null,
"total_amount": null,
"ordered_at": "2026-08-07T09:12:04.311Z",
"created_at": "2026-08-07T09:12:44.512Z",
"updated_at": "2026-08-07T09:12:44.512Z",
"customer": { "name": "Anna Beispiel", "email": null, "phone": null },
"items": [
{
"id": "b7413f52-0e9a-4d77-8c62-2a5f6c31d904",
"sku": "BECHER-GROSS",
"name": "Becher gross",
"quantity": 2,
"price": null,
"product_linked": false,
"production_status": null,
"personalization": { "gravur": "Alles Gute", "name": "Anna" },
"files": []
}
]
},
"warnings": [
{
"code": "item_product_unmatched",
"field": "items[0].sku",
"value": "BECHER-GROSS",
"message": "SKU \"BECHER-GROSS\" is not linked to any product yet. The order was accepted; link it in the dashboard under Products → assignment queue."
}
]
}
}Two things to note:
- Warnings are not errors.
product_linked: falseplusitem_product_unmatchedmeans Meykt does not know that SKU yet. The order was still accepted. Link the SKU once in the dashboard under Products → assignment queue; from then on it matches automatically. - Orders are all-or-nothing. If any item is invalid, the whole order is rejected with
422validation_failedand nothing is created. The response lists *all* field errors at once, with dot-notation paths such asitems.0.quantity. See /developers/errors/validation_failed.
#Step 4: Check the order in the dashboard
Open Orders in the dashboard. The new row carries the Test badge. Open it: customer, quantities, the personalization fields and any attached files are all there.
Test orders behave differently from live orders on purpose:
- They appear only in the order list — not on the dashboard home, not in statistics, not in billing counters.
- They never start production and never trigger workflows or webhooks.
- Test and live are separate numbering spaces:
ORDER-2026-1044used in test does not block the same number in live.
#Step 5: Retry the same request
Send the exact same request from step 3 again. You get 200 — not a duplicate, not a 409.
curl -X POST https://api.meykt.com/v1/orders \
-H "Authorization: Bearer $MEYKT_API_KEY" \
-H "Content-Type: application/json" \
-d @order.jsonThe body has the same shape as in step 3; data.order.id is identical and data.warnings is empty (the warning was reported when the order was first created). Fields are omitted below for brevity:
{
"data": {
"order": {
"id": "8f2c1d0a-6b44-4c11-9a3e-1f0c9d7a5b21",
"external_order_id": "ORDER-2026-1044",
"status": "pending",
"is_test": true
},
"warnings": []
}
}This is what makes retries safe: after a timeout or a dropped connection, resend the identical request. Same external_order_id and same content → the existing order is returned with 200.
Same external_order_id but different content → 409 order_already_exists_with_different_content. Orders are never overwritten through the API. Do not invent a new order number to work around this — use a new external_order_id only for a genuinely new order, and change existing orders in the dashboard. There is no endpoint for updating an order that was already sent. See /developers/errors/order_already_exists_with_different_content.
#Step 6: Go live
- Create a second key in Integrations → API access, this time with the environment Live (prefix
mk_live_), with the same scopes. - Replace the key in your system. Nothing else changes: same base URL, same paths, same request bodies.
- Verify with
GET /v1/pingthat the response now says"environment": "live".
From that point on, orders you send are real: they start production, appear in statistics, trigger automation workflows and, if you have registered endpoints, emit the order.received webhook.
curl https://api.meykt.com/v1/ping \
-H "Authorization: Bearer mk_live_YOUR_KEY"Keep both keys. The test key stays useful for every future change to your integration — you can rehearse the full flow without touching the shop floor.
#Next steps
- Authentication — key format, scopes, rate limits, test vs. live.
- Orders — full field reference, limits (max. 200 items, quantity 1–10,000, max. 50 personalization fields per item), listing with
updated_sinceand cursor paging. - Files — the two-step upload:
POST /v1/uploads, thenPUTthe file content to the returnedupload_url, then reference theupload_idinitems[].files. - Webhooks — event types, signature verification, retries. There is deliberately no "shipped" event, because Meykt has no shipping flow. If you cannot expose a public endpoint, poll
GET /v1/orders?updated_since=…instead — it is a fully supported path. - Error reference — every stable error code with cause and fix. Each API error response also carries a
doc_urlpointing directly at the matching page. - OpenAPI specification — the machine-readable source; generate a client from it instead of transcribing endpoints.
Machine-readable version of this page: https://www.meykt.com/developers/quickstart.md