Skip to content

Overview

Overview

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.

Base URL

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.

Versions

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.

Requests and responses

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.

Operation names

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.

Authentication

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.

Publishable key

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.

Secret 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.

Integration key

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.

Member key

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.

Which key each group of routes takes

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

Errors

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 that does not match its route

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.

Refusals any route can answer

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.

Rate limits

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.

A studio session whose address is not verified

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.

Information

  • OpenAPI version: 3.1.0

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].

Status · Terms of service · Privacy policy · Subprocessors