# HeyMadeIt Printer API — integration guide Version: v1. Contract: https://onprint-test.heymadeit.com/api/v1/openapi.json. Interactive reference: https://onprint-test.heymadeit.com/developers. ## Hostname and authentication Base URL: `https://YOUR-PRINTER-HOST/api/v1`. Replace YOUR-PRINTER-HOST with your assigned printer hostname; heymadeit.com is the public documentation host, not the hostname for your merchant key. Create a scoped key as printer owner/admin at `/portal/platform/credentials`. Send `Authorization: Bearer YOUR_API_KEY` and `Accept: application/json`. For JSON writes also send `Content-Type: application/json`. Keys, users and resources belong to that printer. Use the least scopes required, rotate keys and revoke unused ones. Do not put keys in URLs, browser application code or AI prompts. Interactive request execution is available on the docs served by your printer hostname; authorization is not persisted by Swagger UI. ## First integration 1. Configure products, quantity prices, shipping methods and allowed embed origins on the printer platform. 2. Find a customer with `GET /customers?email=...`; create with `POST /customers` if missing. Your backend must map the logged-in shopper to its stored customer_id; never trust a browser-supplied customer_id without checking it. 3. Browse `GET /templates`; create a design with `POST /designs` and customer_id, title and either a format preset or trim dimensions. For copying a template, give template UUID + width_mm + height_mm and omit format. Shared and your own printer templates are visible; other printers’ own templates are excluded. 4. Create an editor session server-side with `POST /editor-sessions`, then return only the one-use session_token to your authenticated shopper. Load `/embed/v1/sdk.js` from your printer host and pass the token as `sessionToken` to `window.HeyMadeIt.open`. Set the exact parent origin on the embed key. The token expires after 300 seconds and is redeemed once via POST; never put it in a URL. 5. For a file workflow, call `POST /designs/{design}/exports` with format=pdf_print. It returns 202, not a finished PDF. Poll `GET /exports/{export}` with backoff. When done, fetch file_url with bearer auth. Stop on failed/canceled; expired or missing files return 404. 6. For print ordering, call `POST /orders` with a stable `Idempotency-Key` and the same JSON body on retry. For external payments, verify the payment on your server before `PATCH /orders/{order}` status=paid. Read order items/print_files and fetch production PDFs with bearer auth. A browser redirect is not proof of payment. There are no promised REST webhook endpoints. Use the documented polling endpoints. Do not assume the REST API publishes templates: use MCP for that operation. ## Response conventions All monetary amounts are integer minor currency units (net_ore, total_ore or amount_minor), never floating point prices. UUID resource IDs are distinct from integer customer_id/shipping_method_id. Product dimensions use the supplied unit; width_mm and height_mm explicitly use millimetres. Design response w/h are internal document dimensions, not trim millimetres. Products, customers, designs and templates return a top-level Laravel paginator: data[], current_page, last_page, per_page, total and next_page_url (nullable). They accept page and per_page (1–100, default 25). Orders return a data[] array of up to 100 newest rows; no page/per_page is implemented. Membership plans and shipping methods return data[] arrays. Subscriptions return a paginator nested in data (rows at data.data), fixed 50 per page. Client licences return allowance, allocated and teams at the top level. Do not assume all list endpoints share one envelope. Success statuses are endpoint-specific: 200 read/update, 201 create, 202 export queued, 204 delete with no JSON body. Client licence POST returns 201 when creating or 200 when resizing. Field-level 422 errors return message + errors mapping field names to arrays of strings. Other errors include message. Unknown or wrong-host resources return 404. Some endpoints return 409 for state or idempotency conflicts. ## Errors and retries - 401: missing, revoked, invalid or wrong-host merchant key. Fix credentials; do not retry blindly. - 403: missing scope, disabled capability or insufficient customer entitlement. Check printer settings and access. - 404: missing resource, wrong tenant, or unavailable/expired file. - 409: state conflict or idempotency mismatch. Reconcile the request/state. - 422: invalid fields; inspect errors. Do not repeat the same invalid body. - 429: wait for Retry-After, then retry with exponential backoff and jitter. Respect operation x-rate-limit where present. - 5xx/network failure: retry safe reads with bounded backoff. Retry order creation only with its original Idempotency-Key and body. Other writes are not guaranteed idempotent; reconcile before retrying. Do not log authorization headers or session tokens. Error messages and validation details can vary; branch on HTTP status and documented field keys rather than exact message text. ## Scopes products:read/write, customers:read/write, templates:read, designs:read/write, editor:write, exports:read/write, orders:read/write, subscriptions:read/write, licences:read/write. Each operation declares x-required-scope. Scope names ending in /write here mean separate :read and :write scopes, not a literal slash. ## MCP and printer-owned templates Connect an MCP-capable assistant to `https://YOUR-PRINTER-HOST/mcp` and authenticate as an authorized printer staff user using the supported OAuth/access-token flow. Merchant REST API keys do not authenticate MCP. Read `template_kit_info`, build a TemplateSpec, call `check_template`, resolve validation errors/warnings, obtain the user’s publication approval, then `publish_template`. The server enforces printer ownership and prefixes local slugs; reuse the original local slug when updating your own template. Never attempt to publish over a shared HeyMadeIt template or another printer’s template. Customers copy published printer templates through their own catalogue and REST template routes. Full MCP setup and tool workflow are in the public MCP manual. ## Code examples Set HMI_BASE_URL to your printer origin (without /api/v1 or trailing slash). Set HMI_API_KEY from your secret manager. These examples run on a server. ### curl ```curl export HMI_BASE_URL="https://YOUR-PRINTER-HOST" # Set HMI_API_KEY securely in your shell / secret manager. curl --fail-with-body "$HMI_BASE_URL/api/v1/templates?per_page=10" \ -H "Authorization: Bearer $HMI_API_KEY" \ -H "Accept: application/json" ``` ### javascript ```javascript // Server-side JavaScript. Never ship this key to the browser. const response = await fetch(`${process.env.HMI_BASE_URL}/api/v1/templates?per_page=10`, { headers: { Authorization: `Bearer ${process.env.HMI_API_KEY}`, Accept: 'application/json', }, }); if (!response.ok) throw new Error(`HeyMadeIt ${response.status}: ${await response.text()}`); const { data } = await response.json(); console.log(data); ``` ### python ```python # Python standard library; run on your server. import json, os, urllib.request request = urllib.request.Request( os.environ['HMI_BASE_URL'] + '/api/v1/templates?per_page=10', headers={'Authorization': 'Bearer ' + os.environ['HMI_API_KEY'], 'Accept': 'application/json'}, ) with urllib.request.urlopen(request, timeout=30) as response: print(json.load(response)['data']) ``` # REST API reference OpenAPI 3.1.0; API version v1. JSON schema: https://onprint-test.heymadeit.com/api/v1/openapi.json Schemas below describe the current API. Error responses use JSON {"message":"..."}; 422 additionally includes errors as an object mapping field names to arrays of messages. Rate-limited responses may include Retry-After in seconds. Additional model metadata may be returned; tolerate unknown response fields. Examples use fictional identifiers which must be replaced by your own tenant resources. ## GET /api/v1/orders List orders Operation ID: `api_commerce_orders_index` Print orders, verified payment updates and production files. Required scope: `orders:read`. All resources are scoped to this printer hostname. Endpoint throttle: 60 requests per 60 seconds. Respect Retry-After on 429. ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "status": { "type": "string" }, "totalOre": { "type": "integer" } } } } } } } } } } ``` ## POST /api/v1/orders Create orders Operation ID: `merchant-api_post_orders` Print orders, verified payment updates and production files. Required scope: `orders:write`. All resources are scoped to this printer hostname. Idempotency-Key is required (1–120 letters, digits, underscores, dots, colons or hyphens). Reuse it for the same request body on retries; a different body with the same key returns 409. Shipping addresses are required when the shipping method requires one. Payment verification belongs on your server. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string", "maxLength": 120, "minLength": 1, "pattern": "^[A-Za-z0-9_.:-]{1,120}$" }, "example": "shop-order-1042" } ] ``` ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "customer_id": { "type": "integer", "minimum": 1 }, "payment_method": { "enum": [ "manual", "on_account", "external" ] }, "shipping_method_id": { "type": "integer" }, "shipping_address": { "type": "object", "required": [ "name", "line1", "postal_code", "city", "country" ], "properties": { "name": { "type": "string" }, "line1": { "type": "string" }, "line2": { "type": "string" }, "postal_code": { "type": "string" }, "city": { "type": "string" }, "country": { "type": "string", "minLength": 2, "maxLength": 2 } } }, "lines": { "type": "array", "items": { "type": "object", "required": [ "product", "design", "quantity" ], "properties": { "product": { "type": "string", "format": "uuid" }, "design": { "type": "string", "format": "uuid" }, "quantity": { "type": "integer", "minimum": 1, "maximum": 100000 } } }, "minItems": 1, "maxItems": 100 } }, "required": [ "customer_id", "payment_method", "shipping_method_id", "lines" ] }, "example": { "customer_id": 123, "payment_method": "external", "shipping_method_id": 1, "lines": [ { "product": "00000000-0000-7000-8000-000000000001", "design": "00000000-0000-7000-8000-000000000001", "quantity": 100 } ] } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "status": { "type": "string" }, "total_ore": { "type": "integer" } } } } } } } } } ``` ## GET /api/v1/orders/{order} Get orders Operation ID: `api_commerce_orders_show` Print orders, verified payment updates and production files. Required scope: `orders:read`. All resources are scoped to this printer hostname. Endpoint throttle: 60 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "order", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "status": { "type": "string" }, "total_ore": { "type": "integer" }, "totalOre": { "type": "integer" }, "shipping_address": { "type": [ "object", "null" ] }, "items": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string" }, "quantity": { "type": "integer" }, "copyId": { "type": "integer" }, "print_files": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "status": { "type": "string" }, "url": { "type": [ "string", "null" ] } } } } } } } } } } } } } } } ``` ## PATCH /api/v1/orders/{order} Update orders Operation ID: `merchant-api_orders_update` Print orders, verified payment updates and production files. Required scope: `orders:write`. All resources are scoped to this printer hostname. ### Parameters ```json [ { "name": "order", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "enum": [ "paid", "cancelled" ] } }, "required": [ "status" ] }, "example": { "status": "paid" } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "number": { "type": "integer" }, "status": { "type": "string" } } } } } } } } } ``` ## GET /api/v1/products List products Operation ID: `merchant-api_get_products` Catalogue items and quantity prices. Monetary fields are integer minor currency units. Required scope: `products:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "width": { "type": "integer" }, "height": { "type": "integer" }, "unit": { "type": "string" }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "properties": { "quantity": { "type": "integer" }, "net_ore": { "type": "integer" } } } } } } }, "current_page": { "type": "integer" }, "last_page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "next_page_url": { "type": [ "string", "null" ] } } } } } } } ``` ## POST /api/v1/products Create products Operation ID: `merchant-api_post_products` Catalogue items and quantity prices. Monetary fields are integer minor currency units. Required scope: `products:write`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 150 }, "width": { "type": "integer", "minimum": 1, "maximum": 10000 }, "height": { "type": "integer", "minimum": 1, "maximum": 10000 }, "unit": { "enum": [ "px", "mm", "cm", "in" ] }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "required": [ "quantity", "net_ore" ], "properties": { "quantity": { "type": "integer", "minimum": 1, "maximum": 1000000 }, "net_ore": { "type": "integer", "minimum": 0, "maximum": 100000000 } } }, "minItems": 1, "maxItems": 30 } }, "required": [ "name", "width", "height", "unit", "active", "prices" ] }, "example": { "name": "A5 flyer", "width": 148, "height": 210, "unit": "mm", "active": true, "prices": [ { "quantity": 100, "net_ore": 25000 } ] } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "id": { "type": "integer" }, "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "width": { "type": "integer" }, "height": { "type": "integer" }, "unit": { "type": "string" }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "properties": { "quantity": { "type": "integer" }, "net_ore": { "type": "integer" } } } } } } } } } } } } ``` ## GET /api/v1/products/{product} Get products Operation ID: `merchant-api_get_products_product` Catalogue items and quantity prices. Monetary fields are integer minor currency units. Required scope: `products:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "product", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "id": { "type": "integer" }, "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "width": { "type": "integer" }, "height": { "type": "integer" }, "unit": { "type": "string" }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "properties": { "quantity": { "type": "integer" }, "net_ore": { "type": "integer" } } } } } } } } } } } } ``` ## PUT /api/v1/products/{product} Update products Operation ID: `merchant-api_put_products_product` Catalogue items and quantity prices. Monetary fields are integer minor currency units. Required scope: `products:write`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "product", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 150 }, "width": { "type": "integer", "minimum": 1, "maximum": 10000 }, "height": { "type": "integer", "minimum": 1, "maximum": 10000 }, "unit": { "enum": [ "px", "mm", "cm", "in" ] }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "required": [ "quantity", "net_ore" ], "properties": { "quantity": { "type": "integer", "minimum": 1, "maximum": 1000000 }, "net_ore": { "type": "integer", "minimum": 0, "maximum": 100000000 } } }, "minItems": 1, "maxItems": 30 } }, "required": [ "name", "width", "height", "unit", "active", "prices" ] }, "example": { "name": "A5 flyer", "width": 148, "height": 210, "unit": "mm", "active": true, "prices": [ { "quantity": 100, "net_ore": 25000 } ] } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "id": { "type": "integer" }, "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "width": { "type": "integer" }, "height": { "type": "integer" }, "unit": { "type": "string" }, "active": { "type": "boolean" }, "prices": { "type": "array", "items": { "type": "object", "properties": { "quantity": { "type": "integer" }, "net_ore": { "type": "integer" } } } } } } } } } } } } ``` ## DELETE /api/v1/products/{product} Archive products Operation ID: `merchant-api_delete_products_product` Catalogue items and quantity prices. Monetary fields are integer minor currency units. Required scope: `products:write`. All resources are scoped to this printer hostname. Archives the product by setting active=false; retains existing orders. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "product", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "204": { "description": "Completed; no response body" } } ``` ## GET /api/v1/customers List customers Operation ID: `merchant-api_get_customers` Find or create customer accounts on this printer only. Required scope: `customers:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } }, { "name": "email", "in": "query", "schema": { "type": "string", "format": "email" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } } }, "current_page": { "type": "integer" }, "last_page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "next_page_url": { "type": [ "string", "null" ] } } } } } } } ``` ## POST /api/v1/customers Create customers Operation ID: `merchant-api_post_customers` Find or create customer accounts on this printer only. Required scope: `customers:write`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 150 }, "email": { "type": "string", "format": "email", "maxLength": 200 }, "locale": { "type": "string", "minLength": 2, "maxLength": 2 } }, "required": [ "name", "email" ] }, "example": { "name": "Alex Example", "email": "alex@example.com", "locale": "en" } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } } } } } } } } ``` ## GET /api/v1/designs List designs Operation ID: `merchant-api_get_designs` Customer-owned editable documents, created blank or copied from a published template. Required scope: `designs:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "owner_id": { "type": "integer" }, "title": { "type": "string" }, "w": { "type": "integer" }, "h": { "type": "integer" }, "head_seq": { "type": "integer" }, "updated_at": { "type": "string", "format": "date-time" } } } }, "current_page": { "type": "integer" }, "last_page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "next_page_url": { "type": [ "string", "null" ] } } } } } } } ``` ## POST /api/v1/designs Create designs Operation ID: `merchant-api_post_designs` Customer-owned editable documents, created blank or copied from a published template. Required scope: `designs:write`. All resources are scoped to this printer hostname. Choose either a preset format or width_mm + height_mm. With a template, supply the exact trim size and omit format. Flat designs support 1–8 pages; booklet presets use their own sheet constraints. UUIDs identify designs and templates; customer_id is an integer. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "customer_id": { "type": "integer", "minimum": 1 }, "title": { "type": "string", "maxLength": 200 }, "format": { "type": "string", "description": "A preset key; or give width_mm and height_mm." }, "width_mm": { "type": "number", "minimum": 10, "maximum": 5000 }, "height_mm": { "type": "number", "minimum": 10, "maximum": 5000 }, "bleed_mm": { "type": "number", "minimum": 0, "maximum": 50, "default": 3 }, "pages": { "type": "integer", "minimum": 1, "maximum": 8, "default": 1 }, "template": { "type": "string", "format": "uuid", "description": "A published template to clone at width_mm \u00d7 height_mm. Omit it for a blank design." } }, "required": [ "customer_id", "title" ], "oneOf": [ { "required": [ "format" ], "not": { "anyOf": [ { "required": [ "width_mm" ] }, { "required": [ "height_mm" ] }, { "required": [ "bleed_mm" ] }, { "required": [ "template" ] } ] } }, { "required": [ "width_mm", "height_mm" ], "not": { "required": [ "format" ] } } ] }, "example": { "customer_id": 123, "title": "Spring flyer", "width_mm": 148, "height_mm": 210, "pages": 1 } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "owner_id": { "type": "integer" }, "title": { "type": "string" }, "pages": { "type": "integer" } } } } } } } } } ``` ## GET /api/v1/designs/{design} Get designs Operation ID: `merchant-api_get_designs_design` Customer-owned editable documents, created blank or copied from a published template. Required scope: `designs:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "design", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "owner_id": { "type": "integer" }, "title": { "type": "string" }, "w": { "type": "integer" }, "h": { "type": "integer" }, "head_seq": { "type": "integer" }, "updated_at": { "type": "string", "format": "date-time" } } } } } } } } } ``` ## GET /api/v1/templates List templates Operation ID: `merchant-api_get_templates` Published shared templates and this printer’s own templates. Publishing uses MCP, not REST. Required scope: `templates:read`. All resources are scoped to this printer hostname. Contains published HeyMadeIt templates and this printer’s templates, never another printer’s own templates. Both dimensions are required when filtering by size. fields=browse includes description_da and category_name_da. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "category", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "width_mm", "in": "query", "required": false, "schema": { "type": "number" } }, { "name": "height_mm", "in": "query", "required": false, "schema": { "type": "number" } }, { "name": "pages", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "fields", "in": "query", "required": false, "schema": { "type": "string", "enum": [ "browse" ] } }, { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }, { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "slug": { "type": "string" }, "title": { "type": "object" }, "format_key": { "type": "string" }, "category": { "type": "string" }, "pages": { "type": "integer" }, "width_mm": { "type": "number" }, "height_mm": { "type": "number" }, "thumb": { "type": "string" } } } }, "current_page": { "type": "integer" }, "last_page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "next_page_url": { "type": [ "string", "null" ] } } } } } } } ``` ## GET /api/v1/templates/{template} Get templates Operation ID: `merchant-api_get_templates_template` Published shared templates and this printer’s own templates. Publishing uses MCP, not REST. Required scope: `templates:read`. All resources are scoped to this printer hostname. Endpoint throttle: 120 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "template", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "slug": { "type": "string" }, "title": { "type": "object" }, "format_key": { "type": "string" }, "category": { "type": "string" }, "pages": { "type": "integer" }, "width_mm": { "type": "number" }, "height_mm": { "type": "number" }, "thumb": { "type": "string" } } } } } } } } } ``` ## POST /api/v1/editor-sessions Create editor sessions Operation ID: `merchant-api_editor-sessions` One-use, five-minute sessions for embedding the editor without exposing a merchant key. Required scope: `editor:write`. All resources are scoped to this printer hostname. The design must belong to customer_id. parent_origin must exactly match an origin allowed on embed_key. Redeem session_token once by POST /embed/session, or pass it as sessionToken to the embed SDK. It expires after 300 seconds. Never put it in a URL or logs. The response uses Cache-Control: no-store. Endpoint throttle: 60 requests per 60 seconds. Respect Retry-After on 429. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "customer_id": { "type": "integer", "minimum": 1 }, "design": { "type": "string", "format": "uuid" }, "embed_key": { "type": "string" }, "parent_origin": { "type": "string", "format": "uri" }, "finish": { "enum": [ "save", "basket" ], "default": "save", "description": "The finish button: \"Gem og forts\u00e6t\" or \"L\u00e6g i kurv\"." } }, "required": [ "customer_id", "design", "embed_key", "parent_origin" ] }, "example": { "customer_id": 123, "design": "00000000-0000-7000-8000-000000000001", "embed_key": "YOUR_EMBED_KEY", "parent_origin": "https://shop.example.com", "finish": "save" } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "session_token": { "type": "string", "description": "One-use token, redeemed by POST /embed/session." }, "expires_in": { "type": "integer", "const": 300 }, "redeem_url": { "type": "string", "format": "uri" } } } } } } } ``` ## GET /api/v1/membership-plans List memberships Operation ID: `merchant-api_membership-plans` Printer-owned plans for its customers; separate from HeyMadeIt printer subscriptions. Required scope: `subscriptions:read`. All resources are scoped to this printer hostname. ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "amount_minor": { "type": "integer" }, "currency": { "type": "string" }, "interval": { "type": "string", "enum": [ "month", "year" ] }, "checkout_url": { "type": [ "string", "null" ] }, "active": { "type": "boolean" } } } } } } } } } } ``` ## GET /api/v1/subscriptions List subscriptions Operation ID: `merchant-api_subscriptions_index` Sync your payment provider’s verified membership state from your server. Required scope: `subscriptions:read`. All resources are scoped to this printer hostname. ### Parameters ```json [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "user_id": { "type": "integer" }, "merchant_plan_id": { "type": "integer" }, "external_id": { "type": "string" }, "status": { "type": "string", "enum": [ "active", "trialing", "past_due", "cancelled" ] }, "current_period_end": { "type": "string", "format": "date-time" } } } }, "current_page": { "type": "integer" }, "last_page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "next_page_url": { "type": [ "string", "null" ] } } } } } } } } } ``` ## POST /api/v1/subscriptions Create subscriptions Operation ID: `merchant-api_subscriptions_sync` Sync your payment provider’s verified membership state from your server. Required scope: `subscriptions:write`. All resources are scoped to this printer hostname. Only call after verifying your payment provider on your server. current_period_end must be in the future for active or trialing status. Reusing external_id for a different customer returns 409. The printer must enable subscriptions. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "customer_id": { "type": "integer", "minimum": 1 }, "plan": { "type": "string", "format": "uuid" }, "external_id": { "type": "string", "maxLength": 150 }, "status": { "enum": [ "active", "trialing", "past_due", "cancelled" ] }, "current_period_end": { "type": "string", "format": "date-time" } }, "required": [ "customer_id", "plan", "external_id", "status", "current_period_end" ] }, "example": { "customer_id": 123, "plan": "00000000-0000-7000-8000-000000000001", "external_id": "provider-subscription-1042", "status": "active", "current_period_end": "2099-12-31T23:59:59Z" } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "user_id": { "type": "integer" }, "merchant_plan_id": { "type": "integer" }, "external_id": { "type": "string" }, "status": { "type": "string", "enum": [ "active", "trialing", "past_due", "cancelled" ] }, "current_period_end": { "type": "string", "format": "date-time" } } } } } } } } } ``` ## POST /api/v1/designs/{design}/exports Create exports Operation ID: `merchant-api_exports_store` Asynchronous document rendering and authenticated downloads. Required scope: `exports:write`. All resources are scoped to this printer hostname. POST queues a render (202). Poll GET /exports/{export} until status=done or a terminal failed/canceled state; back off between polls. Download file_url with exports:read; it is null until done. Expired or unavailable files return 404. No webhook delivery is promised. PDF Print supports marks, bleed and bleed_mm. Endpoint throttle: 30 requests per 60 seconds. Respect Retry-After on 429. ### Parameters ```json [ { "name": "design", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "format" ], "properties": { "format": { "enum": [ "png", "jpg", "pdf_standard", "pdf_print" ] }, "pages": { "type": "array", "items": { "type": "string" }, "maxItems": 200 }, "marks": { "type": "boolean", "description": "Crop marks; also the bleed unless `bleed` is given." }, "bleed": { "type": "boolean", "description": "PDF Print: the document bleed, with or without marks." }, "bleed_mm": { "type": "number", "minimum": 0, "maximum": 50, "description": "PDF Print: the bleed in mm a print shop's data sheet asks for, less or more than the document's own (art on the trim grows into it)." }, "layout": { "enum": [ "pages", "spreads" ], "default": "spreads" }, "scale": { "type": "number", "minimum": 0.5, "maximum": 4 } } }, "example": { "format": "pdf_print", "marks": true, "bleed": true, "bleed_mm": 3, "layout": "pages" } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "202": { "description": "Render queued; poll the export status", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "status": { "enum": [ "queued", "rendering", "encoding", "done", "failed", "canceled" ] }, "progress": { "type": "integer" }, "format": { "type": "string" }, "expires_at": { "type": [ "string", "null" ] }, "file_url": { "type": [ "string", "null" ], "description": "Relative download URL when done; requires exports:read." } } } } } } } } } ``` ## GET /api/v1/exports/{export} Get exports Operation ID: `merchant-api_exports_show` Asynchronous document rendering and authenticated downloads. Required scope: `exports:read`. All resources are scoped to this printer hostname. POST queues a render (202). Poll GET /exports/{export} until status=done or a terminal failed/canceled state; back off between polls. Download file_url with exports:read; it is null until done. Expired or unavailable files return 404. No webhook delivery is promised. PDF Print supports marks, bleed and bleed_mm. ### Parameters ```json [ { "name": "export", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "status": { "enum": [ "queued", "rendering", "encoding", "done", "failed", "canceled" ] }, "progress": { "type": "integer" }, "format": { "type": "string" }, "expires_at": { "type": [ "string", "null" ] }, "file_url": { "type": [ "string", "null" ], "description": "Relative download URL when done; requires exports:read." } } } } } } } } } ``` ## GET /api/v1/exports/{export}/file Get exports Operation ID: `merchant-api_exports_file` Asynchronous document rendering and authenticated downloads. Required scope: `exports:read`. All resources are scoped to this printer hostname. POST queues a render (202). Poll GET /exports/{export} until status=done or a terminal failed/canceled state; back off between polls. Download file_url with exports:read; it is null until done. Expired or unavailable files return 404. No webhook delivery is promised. PDF Print supports marks, bleed and bleed_mm. ### Parameters ```json [ { "name": "export", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } } } ``` ## GET /api/v1/shipping-methods List shipping Operation ID: `merchant-api_shipping_index` Delivery choices and their net prices. Required scope: `products:read`. All resources are scoped to this printer hostname. ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "net_ore": { "type": "integer" }, "active": { "type": "boolean" }, "requires_address": { "type": "boolean" } } } } } } } } } } ``` ## POST /api/v1/shipping-methods Create shipping Operation ID: `merchant-api_shipping_store` Delivery choices and their net prices. Required scope: `products:write`. All resources are scoped to this printer hostname. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "name", "net_ore", "active" ], "properties": { "name": { "type": "string", "maxLength": 100 }, "net_ore": { "type": "integer", "minimum": 0, "maximum": 10000000 }, "active": { "type": "boolean" }, "requires_address": { "type": "boolean" } } }, "example": { "name": "Collection", "net_ore": 0, "active": true, "requires_address": false } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "net_ore": { "type": "integer" }, "active": { "type": "boolean" }, "requires_address": { "type": "boolean" } } } } } } } } } ``` ## GET /api/v1/print-files/{printFile} Get print files Operation ID: `merchant-api_print-file` Authenticated order production PDF downloads. Required scope: `orders:read`. All resources are scoped to this printer hostname. ### Parameters ```json [ { "name": "printFile", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } } } ``` ## GET /api/v1/client-licences List client licences Operation ID: `merchant-api_client-licences_index` Allocate the printer’s Business employee seat allowance across client company teams. Required scope: `licences:read`. All resources are scoped to this printer hostname. Light / Pro / Plus include 5 / 15 / 50 Business employee seats in total. Team owners and pending invitations consume seats. POST without team creates a team (201); with team resizes it (200). DELETE releases allocation without deleting designs or the team. These are named employee seats, not unlimited company subscriptions. ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "allowance": { "type": "integer" }, "allocated": { "type": "integer" }, "teams": { "type": "array", "items": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "owner_id": { "type": "integer" }, "seats": { "type": "integer" }, "occupied": { "type": "integer" }, "owner": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } } } } } } } } } } } ``` ## POST /api/v1/client-licences Create client licences Operation ID: `merchant-api_client-licences_store` Allocate the printer’s Business employee seat allowance across client company teams. Required scope: `licences:write`. All resources are scoped to this printer hostname. Light / Pro / Plus include 5 / 15 / 50 Business employee seats in total. Team owners and pending invitations consume seats. POST without team creates a team (201); with team resizes it (200). DELETE releases allocation without deleting designs or the team. These are named employee seats, not unlimited company subscriptions. ### JSON request body ```json { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "customer_id", "name", "seats" ], "properties": { "customer_id": { "type": "integer", "minimum": 1 }, "name": { "type": "string", "maxLength": 100 }, "seats": { "type": "integer", "minimum": 1, "maximum": 10000 }, "team": { "type": "string", "format": "uuid", "description": "Omit to create a client company team; supply its UUID to resize its allocation." } } }, "example": { "customer_id": 123, "name": "Example Company", "seats": 5 } } } } ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "owner_id": { "type": "integer" }, "printer_licence_seats": { "type": "integer" } } } } } } } }, "200": { "description": "Existing team allocation resized", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "uuid": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "owner_id": { "type": "integer" }, "printer_licence_seats": { "type": "integer" } } } } } } } } } ``` ## DELETE /api/v1/client-licences/{team} Release client licences Operation ID: `merchant-api_client-licences_destroy` Allocate the printer’s Business employee seat allowance across client company teams. Required scope: `licences:write`. All resources are scoped to this printer hostname. Light / Pro / Plus include 5 / 15 / 50 Business employee seats in total. Team owners and pending invitations consume seats. POST without team creates a team (201); with team resizes it (200). DELETE releases allocation without deleting designs or the team. These are named employee seats, not unlimited company subscriptions. ### Parameters ```json [ { "name": "team", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ] ``` ### Responses ```json { "401": { "description": "Missing, unknown or revoked API key" }, "403": { "description": "Required scope or tenant capability missing" }, "404": { "description": "Resource does not belong to this tenant" }, "409": { "description": "Order state or idempotency conflict" }, "422": { "description": "Validation failed; errors maps field names to messages" }, "429": { "description": "Rate limit reached; retry with backoff" }, "204": { "description": "Completed; no response body" } } ``` # Public printer and MCP manuals ## Printer manual Set up your platform, give client employees Business access, and let customers design and order. ### What you are buying Your printer subscription combines Business user licences for client companies, an editor you can embed on your existing website, REST API access, and your own template publishing through MCP. A branded hosted portal with simple print commerce is an alternative to building a webshop integration. ### Choose where customers work Use the embedded editor if you already have a website and checkout. Use the hosted portal if you want customers to browse templates, create designs and buy your print products under your brand. You can operate both. On Pro and Plus, connect your own verified domain. ### Set up the printer portal Sign in on your printer hostname as an owner or administrator and open Print platform (/portal/platform). Add your logo, brand name, colours, contact details and language. Preview the portal. Choose commerce off, print, subscriptions or hybrid. Configure products, quantity prices and shipping before accepting orders. ### Give a client company its licences Create the client owner as a customer, then open Client licences in the printer portal. Allocate seats to that company. The owner counts as one seat and adds colleagues in team settings; pending invitations reserve seats. Light includes 5 seats in total, Pro 15 and Plus 50, shared across your client companies. Each company has its own team projects, brand kit and members. Webshop shoppers are separate from these employee licences. ### Prepare your own templates Connect an MCP assistant to your printer hostname using a staff account with template management permission. Ask it to read template_kit_info, build a TemplateSpec, run check_template and fix every error and warning. After you approve publication, publish_template makes it available on your printer platform. The server assigns ownership and a namespace; you cannot overwrite the HeyMadeIt catalogue or another printer. ### Test a complete order before launch Use a test customer to open a published template, change text and images, save, add a print product and quantity, choose shipping, and place an order. Check the order and production PDF in the portal. For an integration, also test session expiry, a cancelled payment, duplicate order requests and an export that fails preflight. ## User manual Create and edit designs, work with colleagues, and send your work to print. ### Open the right account If a printer invited you, use the link and hostname in that invitation. Accept team invitations with the email address they were sent to. Choose your company team before creating shared work. Your personal space and each company team have separate projects and uploads. ### Start from a template Browse templates and choose the right product and size. Opening a template creates your own editable design; it does not change the original template. Select text to change it, replace photos with uploads, and use the brand kit for company logos, colours and fonts. ### Edit, save and organise Use the toolbar for text, colours, images and alignment. Add pages only when the product supports them. Wait for saving to finish before leaving the editor. Name your design and organise it in folders. In an embedded editor, use Save and continue or Add to basket to return control to the shop. ### Work with colleagues A Business licence gives you the company team workspace, shared brand kit, collaboration and comments. Team owners and administrators manage membership. Share with the appropriate view, comment or edit permission. Avoid a public link when a design contains private information. Version history lets you inspect earlier saved work. ### Check your print file Check spelling, contact details, page order and image quality. Keep text inside the safe area and extend backgrounds into the bleed. Read preflight findings before exporting a print PDF. For a folded card, review the outside and inside in physical reading order. Approve the proof with your printer before production. ### Order or download Choose the product, quantity and delivery offered by your printer. Review the price and proof before ordering. A saved design is not an order, and an order is not proof of payment. Download options depend on your account and the printer agreement. Contact your printer for order, payment and delivery questions. ### Use your AI assistant Connect your assistant to the MCP server for the same site and account. Ask it to find a template, insert your supplied text and images, and check the result. Open the returned editor link to review and continue editing. The assistant acts with your account permissions. Ordinary users cannot publish into the printer template library. ## REST API and embed guide A server-to-server quickstart, the public OpenAPI reference, and a secure browser embed. ### Base URL, credentials and scopes Call https://YOUR-PRINTER-HOST/api/v1 on the hostname assigned to your printer. The public reference is /api/v1/openapi.json on that host; the HeyMadeIt host also serves the route-derived reference. Create scoped keys under /portal/platform/credentials. Send Authorization: Bearer YOUR_API_KEY and Accept: application/json. Keys only work for their own printer host. Keep keys on your server, rotate them when needed, and revoke unused keys. Open the interactive developer portal at /developers for searchable Swagger documentation, copyable code examples, and an importable Postman collection. AI agents can read /llms.txt, /llms-full.txt and /developers/reference.md directly. ### Find or create a customer Use GET /customers?email=person%40example.com to find your mapped customer. POST /customers requires name and email; locale is optional. Store the returned data.id against your own authenticated customer. Never let the browser choose another customer_id. Customer creation does not assign a Business licence or send a sign-in email. ``` POST /api/v1/customers {"name":"Example customer","email":"person@example.com","locale":"en"} ``` ### Find a template and create an editable design GET /templates supports category, width_mm, height_mm, pages and fields=browse. It returns published HeyMadeIt templates and your own printer templates; another printer’s templates are excluded. GET /templates/{uuid} reads one. Create a design with customer_id, title and either a format preset or width_mm and height_mm. With template, supply the trim size; omit format. For the standard API, flat designs support up to eight pages. ``` POST /api/v1/designs {"customer_id":123,"title":"Business card","width_mm":85,"height_mm":55,"bleed_mm":3,"pages":1,"template":"TEMPLATE_UUID"} ``` ### Issue a one-use editor session Create an embed key and allow the exact webshop origin, including https and any port. Your backend calls POST /editor-sessions for the authenticated customer’s design. parent_origin must be allowed. finish is save or basket. The response contains session_token, expires_in (300 seconds) and redeem_url. The token can be redeemed once by POST; it must not be placed in a URL. Never return the API key to the browser. ``` POST /api/v1/editor-sessions {"customer_id":123,"design":"DESIGN_UUID","embed_key":"PUBLIC_EMBED_KEY","parent_origin":"https://shop.example.com","finish":"basket"} ``` ### Open the browser SDK Serve the SDK from your printer host. Your own /design-session endpoint must authenticate the shopper and request a fresh server-side session. The SDK opens an iframe and validates messages by origin and frame. onComplete means the design was saved; your backend must verify ownership, product, quantity and payment before placing an order. close() removes the iframe. ```
``` ### Queue and download a print PDF POST /designs/{uuid}/exports with format pdf_print returns HTTP 202 and data.uuid. Poll GET /exports/{uuid}; status done provides file_url. Download that relative URL from your server with the same bearer key. Other states include queued, rendering, encoding, failed and canceled; handle failures and expiry explicitly. layout can be pages or spreads. Use bleed:true for document bleed, and marks:true for crop marks. ``` POST /api/v1/designs/DESIGN_UUID/exports {"format":"pdf_print","bleed":true,"marks":true,"layout":"pages"} ``` ### Products, shipping and idempotent orders Products use width, height, unit and quantity prices in minor currency units (net_ore). Shipping uses /shipping-methods. POST /orders requires an Idempotency-Key header, customer_id, payment_method, shipping_method_id and lines with product, design and quantity. Reuse the key only for the same order body. Read GET /orders/{uuid} for production files. Use PATCH /orders/{uuid} with paid or cancelled only after your server has verified the payment or cancellation. For external payment, HeyMadeIt does not verify the provider for you. ``` POST /api/v1/orders Idempotency-Key: shop-order-1042 {"customer_id":123,"payment_method":"external","shipping_method_id":7,"lines":[{"product":"PRODUCT_UUID","design":"DESIGN_UUID","quantity":100}]} ``` ### Business licence allocation GET /client-licences returns allowance, allocated and company teams. POST /client-licences requires customer_id (the company owner), name and seats. Omit team to create a company team; send an existing team UUID to resize it. All teams draw from the same printer allowance. API keys need licences:read or licences:write. The owner and pending invitations count toward the company’s seats. DELETE /client-licences/{team} releases the company allocation without deleting its work. ``` POST /api/v1/client-licences {"customer_id":123,"name":"Example company","seats":3} ``` ### Errors, pagination and rate limits 401 means a missing, revoked or wrong-host key. 403 means missing scope, a disabled integration or insufficient access. 404 also protects resources owned by another tenant. 409 signals a state or idempotency conflict. 422 returns validation errors. 429 means wait and retry with backoff. Lists expose data and pagination metadata; most catalogue lists accept per_page up to 100. Respect endpoint throttles in the OpenAPI reference. MCP uses user OAuth/access tokens, not these merchant API keys. ## MCP and AI assistants Connect an assistant to the correct account, create editable designs, and publish printer-owned templates. ### Connect to your own platform MCP (Model Context Protocol) lets an assistant use HeyMadeIt tools with your permission. For HeyMadeIt accounts use https://heymadeit.com/mcp. For a printer platform use https://YOUR-PRINTER-HOST/mcp and sign in with that platform’s account. Use OAuth in clients that support it, or the access token method shown in Design with AI. A merchant REST API key is not an MCP token. Never paste credentials into a design brief. ### Confirm the account and workspace Start with whoami. Check the account and available teams, and explicitly choose the intended company space. If the wrong account appears, disconnect and reconnect on the correct hostname. Tool visibility follows your role; the assistant does not gain administrator rights. ### Create a design with an assistant Ask the assistant to search_templates, inspect get_template, and use create_from_template for your chosen template. It can change text and images, manage pages and folders, use your brand kit, and run check_design. Review its preview and open the returned editor_url exactly as supplied. export_design and get_export prepare and retrieve supported downloads. Plans and print preflight still apply. ``` Create an A5 flyer in our company team using our brand kit. Find a suitable template, insert the text I supply, show me a preview, and fix any check_design findings. Leave missing dates and prices as placeholders. ``` ### Publish a printer template Printer owners, administrators and designers with template management permission get template_kit_info, check_template, publish_template and template_import_status. Read the kit and schema, write a complete TemplateSpec with Danish and English text, check it, inspect previews and fix all errors and warnings. Publish only after the user requests publication. Check and publish are separate calls. ``` On this printer platform, create an A5 template for our customers. Read template_kit_info overview, schema, formats, palettes and fonts. Use local slug summer-offer. Run check_template and show me the previews. Wait for my publication instruction before calling publish_template. ``` ### Ownership and customer visibility On your printer host, publishing always targets your printer. The server prefixes the local slug with p followed by your tenant number; keep the local slug short enough for the final slug’s 64-character limit. Reuse your original local slug to update your own template. Do not send the generated prefix back as the local slug. Published templates appear in that printer’s catalogue and template API for customers to copy. They do not enter HeyMadeIt’s shared catalogue or another printer’s platform. Printer templates are visible to all customers of that printer; this is not a private template per client company. ### Wait for the result A render may outlast one MCP call. If the response includes run_id and queued or running status, call template_import_status with that run_id after the suggested delay. Publication is confirmed only when outcome is published and the returned format statuses are published. Keep the returned public_urls and test them on your printer hostname. A queued run or clean check is not publication. ### Access and troubleshooting If publishing tools are missing, confirm the printer hostname, staff role and template permission, then refresh the client’s tool list. Customers can edit their own designs but cannot publish templates. A revoked user permission is checked again when queued publication runs. For rejected JSON, read the exact schema and feedback rather than retrying unchanged content. Disconnect or revoke the assistant token when access is no longer needed. ## White-label and simple ecommerce Run a branded design and print portal, or keep your existing checkout. ### Hosted portal or existing webshop The hosted portal provides template browsing, an editor, print products with quantity prices, a basket, shipping choices and orders. You supply branding, products, prices and customer support. If you already run ecommerce, embed the editor and keep your current product catalogue, checkout and payment provider. ### Brand and domain Use Print platform to configure logo, colours, contact details, theme and language. Pro and Plus include an own-domain setup. Add the domain in the platform panel, follow its DNS instructions and verify it before making it primary. Domain verification alone does not prove HTTPS is ready; test the public hostname and certificate. ### Choose the commerce mode Off disables native commerce. Print sells print products. Subscriptions shows your customer membership offers. Hybrid combines print and memberships. Your client-company Business licence allocation is separate from customer membership products. A membership checkout URL sends the customer to your configured provider; your backend must verify payment and sync /subscriptions. ### Payments and production Choose supported payment methods for your printer setup. With external checkout, confirm payment on your server and update the order through the API; a browser redirect is not payment confirmation. Review order status and the production PDF before fulfilment. This is simple print commerce: custom tax, accounting, payment and shipping integrations require their own setup. ## Plans and client licences Understand the employee licences, print PDF allowance and integration in each printer plan. ### Light: 5 Business user licences Light includes five Business employee seats in total, 50 print PDFs per month, the embedded editor, REST API, your logo and colours, the shared template library, and publishing your own printer templates through MCP. Allocate the seats across client company teams. Five seats are five named users, including team owners; they are not five unlimited companies. ### Pro: 15 Business user licences Pro includes Light’s capabilities, increases the total to 15 Business employee seats and 250 print PDFs per month, and adds white-label on your own domain. A hosted portal with simple print commerce can be configured for your business. ### Plus: 50 Business user licences Plus includes Pro’s capabilities and increases the total to 50 Business employee seats and 1,000 print PDFs per month. Enterprise capacity and integration are agreed separately. The public pricing page shows current prices, setup and overage in your chosen currency. ### Allocate and resize a company team In Client licences, enter the existing customer owner ID, company name and seat count. The owner receives a Business team and can add internal colleagues. Use an existing team UUID to resize its allocation. The total cannot exceed the printer allowance, and a company cannot add users beyond its allocation. Remove users and pending invitations before reducing an allocation. Ordinary shoppers do not consume these team seats. Use Release licences when a client leaves: their seats return to the pool, their Business team access stops, and their designs remain stored. Resize the same team to restore access later. ### Changes to the printer agreement The printer plan is assigned by platform staff. If the licence is suspended or cancelled, allocated Business access stops. If a downgrade leaves more seats allocated than the new allowance, reduce allocations before Business team access resumes. Existing designs are retained. Contact HeyMadeIt to change the printer agreement; customer membership subscriptions are managed separately. ## Print files and troubleshooting Check the saved design, preflight and production file before printing. ### Print PDF settings Use PDF Print for print production. Check the trim size, bleed and crop marks against the product specification. Export pages or spreads to match the production method. For folded cards and brochures, inspect physical page order. Cut paths and named spot plates need a compatible production setup. A successful export is not a visual proof approval. ### Export still queued or failed Poll the export status rather than requesting a fresh export repeatedly. queued is waiting for the render worker, rendering and encoding are processing, done offers the file, and failed needs investigation. Read preflight and error feedback; check missing fonts and images. Download completed files before expiry. A 404 on the download can mean the export is not complete, has expired, or belongs to another printer. ### Editor does not open Check the exact parent origin on the embed key, the printer hostname and the customer’s design ownership. Request a fresh session if the token is older than five minutes or has already been used. Keep the SDK and session on the same printer host. Test browser cookie settings when using an iframe. Never solve an embed error by exposing the API key. ### Template not visible A clean check has not published a template. Read template_import_status and confirm outcome published. Search on the printer host where it was published. A printer-owned template is intentionally absent from the HeyMadeIt platform and other printers. Fix all QA warnings and errors; retry server/render failures only after the underlying service is available. ### Who to contact End customers should contact their printer about orders, payment, delivery, products and account access. Printer administrators should include the hostname, time, order/design/export UUID and non-secret error message when contacting HeyMadeIt. Do not send bearer tokens, API secrets, passwords or customer payment details.