Information
- OpenAPI version:
3.1.0
The Pixonaut API turns a template into a printed product. A merchant publishes templates; a customer fills one in inside the designer on the merchant’s storefront and commits a design; the merchant’s own server approves the design, prints it, and sends the print to a printshop as an order. Each of those steps is also reported as an event, delivered to the merchant’s webhook endpoints.
Start with Design to order, which walks through that flow request by request.
This reference names no server. Send requests to the host that serves it:
the same origin as its /openapi.json. Every example path in it is relative
to that host.
The routes an integration calls are under /v1, and the version is the path
prefix. A few routes sit outside it because they are fetched as plain URLs
rather than called: template images under /t, and the signed links the API
hands out for print files, proof images, uploads and published assets.
Bodies are JSON, sent with content-type: application/json, unless a route
says otherwise (an upload’s bytes, an image, a PDF). Times are milliseconds
since the Unix epoch unless a field says otherwise. Every route in this
reference lists the key it takes, its request and response shapes, and each
refusal it can answer with.
Every operation has an operationId of the form <resource>.<verb>, nested
resources dotted: templates.list, designs.approve,
blanks.placements.fittingTemplates.list. The SDKs name their methods from
it, so templates.list is client.templates.list() in JavaScript and
$client->templates->list() in PHP. The routes outside /v1, which are
fetched as plain URLs, are named too and carry x-sdk-skip: true: no SDK
generates a method for them.
A route that takes an API key reads it as a bearer token:
Authorization: Bearer sk_...
A tenant has three kinds of key, and every such route says which it takes. The few routes that take no key are signed links the API hands out, which carry their own proof, and public files; they are listed with the rest below.
A publishable key (pk_...) is meant to sit in a browser, inside the designer on the merchant’s own storefront, so it is visible to anyone who opens the page. It is bound to the web origins the tenant allows, and refused from any other. It can do what a customer needs: read templates, preview, upload a picture and commit a design. It cannot approve, print or order.
A request with a publishable key must carry an Origin header matching one
of the key’s allowedOrigins (set when the key is created with
POST /v1/api-keys), or it is refused with origin_forbidden. An entry is
an exact origin (https://shop.example), or *.shop.example, which matches
shop.example and any subdomain of it. An entry of * alone matches every
origin, which removes the key’s origin binding altogether: any page on any
site can then use it, so it is for trying the API out, never for a live
storefront. Because a browser sets Origin
itself, a publishable key sent from a server with no Origin is refused too:
a server uses its secret key, and a connected store’s server its integration
key.
A secret key (sk_...) stays on the merchant’s server. It acts as the tenant itself, and it is the only credential that approves designs, prints them and submits orders.
Never send it to a browser. Revoke one with DELETE /v1/api-keys/{id} when
whoever holds it no longer should.
An integration key (ik_...) is a connected store’s key, given to the store when a member approves its connection, together with a publishable key and a webhook endpoint. It stays on the store’s server and acts as the tenant, but only within its grants: it reads templates, blanks and printshops, makes and decides designs, makes and reads prints, places and reads orders, and manages the one webhook endpoint it was given. It never registers another endpoint or reaches one it was not given, and never manages keys, members, roles or settings. Pixel in the store’s admin and the MCP endpoint act as a member, so they take the member key a store’s user links instead (member_link_required).
A store gets one by connecting: the merchant starts at the store, approves in
the studio, and the store exchanges the code it is sent back with for its
integration key, a publishable key bound to the store’s origin and a webhook
endpoint at the store (POST /v1/connect/token). Nobody copies a key by
hand. A route the key’s grants do not cover refuses it with forbidden.
DELETE /v1/connect disconnects the store: it revokes all three.
A member key (mk_...) is held by a connected store for one of its users, who linked their own Pixonaut account to it (POST /v1/connect/token with kind: "member_link"). It acts as that member, with their permissions: Pixel in the store’s admin, with the conversation theirs in that store, and the MCP endpoint, as an MCP key the member minted would. It holds while the link, the store’s connection and the membership do; after that it is refused member_link_revoked.
A store links one of its users the way it connects: it sends the user to the
studio with kind=member_link, the member approves linking themselves to the
workspace the store is connected to, and the store exchanges the code with its
integration key as the bearer (POST /v1/connect/token with
kind: "member_link"). The store that connects is given its approving
member’s key in the same exchange (memberKey). The store keeps the key for
that user and sends it on Pixel’s routes and the MCP endpoint, which refuse
the integration key itself with member_link_required.
DELETE /v1/connect/member-link unlinks it.
A share link’s token (link_...), such as the one in a correction link a store mints for its customer (POST /v1/designs/{id}/correction-links), sent by the hosted page the link opens. The token is the credential; the api takes it only from the hosted pages’ origin (their Origin header, or none on a read the page sends to its own origin), and a correction link also from the tenant’s store origins, which keeps other web pages from using a link in a browser. A link acts only on its own template, design or session, and a designer link reads the blank it is sold on: any other is answered as not found.
A store never sends this token itself: it hands the link to its customer, and the hosted page sends the token. It is listed on every route a link reaches, so a client validating what a hosted page sends can check it.
The number of routes in each group that take a publishable key, a secret key, an integration key, a member key, a share link, or no key at all. A route with no key is a signed link the API handed out, which carries its own proof, or a public file.
| Group | Publishable key | Secret key | Integration key | Share link | Member key | No key |
|---|---|---|---|---|---|---|
| API keys | none | 4 of 9 | 4 of 9 | none | 1 of 9 | 1 of 9 |
| Assets | 5 of 26 | 19 of 26 | none | 5 of 26 | none | 4 of 26 |
| Automations | none | all | none | none | none | none |
| Blanks | 4 of 7 | all | 6 of 7 | 3 of 7 | none | none |
| Designs | 1 of 27 | all | all | 11 of 27 | none | none |
| Events | none | all | all | none | none | none |
| Fonts | none | none | none | none | none | all |
| Orders | none | all | 14 of 17 | 6 of 17 | none | none |
| Pixel | 1 of 7 | none | none | 1 of 7 | 6 of 7 | none |
| Prints | none | 6 of 8 | 6 of 8 | none | none | 2 of 8 |
| Printshops | none | all | all | none | none | none |
| Proofs | 3 of 4 | 3 of 4 | none | 3 of 4 | none | 1 of 4 |
| Sessions | 1 of 2 | all | none | 1 of 2 | none | none |
| Share links | none | all | all | none | none | none |
| Templates | 6 of 8 | all | 5 of 8 | 3 of 8 | none | none |
| Webhook endpoints | none | all | 7 of 8 | none | none | none |
A refused request answers a 4xx or 5xx status with a JSON body whose error
is a code, and sometimes fields that say more:
{ "error": "design_not_approved" }
{ "error": "payload_too_large", "maxBytes": 65536, "bytes": 70312 }
Branch on error, never on the status alone: one status can carry several
codes, and each route lists the codes it can answer with under each status.
A refusal is an expected answer. It means the request asked for something
that is not there, not yet approved, over a limit or not the key’s to do,
and the code says which.
A request the API failed to handle for a reason of its own answers 500 with the plain-text body Internal Server Error rather than JSON. There is nothing to change in the request, and it can be retried later.
Every response names the request it answers in X-Request-Id: the id the request was sent with in that header, when it is well formed, else one the API gave it. Quote it when reporting a refusal or a failure: the API’s log lines for that request carry it.
Before retrying a request that asked a printshop for something, such as
POST /v1/prints/{id}/orders, read what it was about first: the failure may
have come after the printshop took the order. Check the print’s orders with
GET /v1/orders?printId=<print id> and an order’s status with GET /v1/orders/{id}, and never
submit again while an order says outcome_unknown.
A body, query or path the route cannot accept is refused before the route
does anything, with a 422 or, on some routes, a 400, whose error names
what was wrong. Some carry a field saying more, such as a one-line reason
or an issues list naming each field at fault. Each operation lists the codes
it can answer with.
These come from checks that run before a route’s own work: the key and its
origin, on the routes that take one, the rate limit and the body’s size. They
are also listed in this document’s x-refusals.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized |
The request carries no credential, or one that is unknown or revoked. |
| 401 | member_link_required |
A connected store’s integration key was sent to a route that acts as a member. The store links its user’s Pixonaut account first (POST /v1/connect/token with kind: "member_link") and sends the member key it is given. |
| 401 | member_link_revoked |
A member key whose link no longer holds: it was revoked, its member left the workspace, or its store was disconnected. The store asks its user to link their account again. The store’s own connection is not affected. |
| 403 | origin_forbidden |
A publishable key was sent from an origin outside the key’s allowlist. |
| 403 | secret_key_required |
The route takes a secret key, and the request carried another credential. |
| 403 | forbidden |
The route does not take the kind of credential sent. |
| 403 | feature_disabled |
The tenant does not have the feature the key is for: api for a publishable or secret key, mcp for an MCP key. The key is kept, and is admitted again once the tenant has the feature. |
| 403 | workspace_suspended |
The workspace the credential belongs to is suspended. Every credential of it is refused until the suspension is lifted; nothing of it is deleted. |
| 429 | rate_limited |
Too many requests in the current window. Retry after the number of seconds in retry-after. |
| 422 | payload_too_large |
The request body is larger than the route accepts. maxBytes is the limit. |
| 500 | db_error |
The api could not read or write its database. |
Each caller has a budget per bucket: an api key, a member, a person’s MCP
keys together, or one visitor of a storefront’s publishable key or share
link. All the visitors of one publishable key or link share a total, and all
of a workspace’s callers share a ceiling, so one busy store leaves its
members room. The budgets are the same on every plan. A request past one is
refused with 429 and rate_limited, and the retry-after header says how
many seconds to wait; the windows slide, so the budget comes back gradually.
| Bucket | Counts | Window | One caller | A storefront key’s or link’s visitors together | The workspace together |
|---|---|---|---|---|---|
proof |
Proofs: each one rendered | a minute | 30 | 90 | 150 |
validate |
Validating a design | a minute | 120 | 360 | 600 |
commit |
Committing a design | a minute | 30 | 90 | 150 |
pixel |
Pixel’s turns | a minute | 20 | 60 | 100 |
pixelUpload |
Files attached to Pixel | a minute | 60 | 180 | 300 |
webhookPing |
Test requests to a webhook endpoint | a minute | 10 | 30 | 50 |
blankImport |
Importing blanks from a CSV, and its previews | a minute | 10 | 30 | 50 |
mcp |
MCP requests, per person across their MCP keys | a minute | 120 | 360 | 600 |
Public file addresses, hosted designer pages and share links are also limited per visitor’s address, and answer the same way.
The studio’s own session, signed in to an account whose email address is not
verified yet, is refused every write with 403 and email_not_verified,
whatever the route, except accepting an invitation, which verifies the
address. No key or integration meets it: the rule is about the signed-in
person.
An integration key (ik_...) is a connected store’s key, given to the store when a member approves its connection, together with a publishable key and a webhook endpoint. It stays on the store’s server and acts as the tenant, but only within its grants: it reads templates, blanks and printshops, makes and decides designs, makes and reads prints, places and reads orders, and manages the one webhook endpoint it was given. It never registers another endpoint or reaches one it was not given, and never manages keys, members, roles or settings. Pixel in the store’s admin and the MCP endpoint act as a member, so they take the member key a store’s user links instead (member_link_required).
Security scheme type: http
A member key (mk_...) is held by a connected store for one of its users, who linked their own Pixonaut account to it (POST /v1/connect/token with kind: "member_link"). It acts as that member, with their permissions: Pixel in the store’s admin, with the conversation theirs in that store, and the MCP endpoint, as an MCP key the member minted would. It holds while the link, the store’s connection and the membership do; after that it is refused member_link_revoked.
Security scheme type: http
A publishable key (pk_...) is meant to sit in a browser, inside the designer on the merchant’s own storefront, so it is visible to anyone who opens the page. It is bound to the web origins the tenant allows, and refused from any other. It can do what a customer needs: read templates, preview, upload a picture and commit a design. It cannot approve, print or order.
Security scheme type: http
A secret key (sk_...) stays on the merchant’s server. It acts as the tenant itself, and it is the only credential that approves designs, prints them and submits orders.
Security scheme type: http
A share link’s token (link_...), such as the one in a correction link a store mints for its customer (POST /v1/designs/{id}/correction-links), sent by the hosted page the link opens. The token is the credential; the api takes it only from the hosted pages’ origin (their Origin header, or none on a read the page sends to its own origin), and a correction link also from the tenant’s store origins, which keeps other web pages from using a link in a browser. A link acts only on its own template, design or session, and a designer link reads the blank it is sold on: any other is answered as not found.
Security scheme type: http
Questions? Write to [email protected].