# 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'])
```
