The API: ordering from your own system
19 min readUpdated
The Reseller API connects your own web store, ERP or script directly to Printen.eu. In this article you create a key, fetch the catalogue, request a quote and place your first order, first in the sandbox and then live. The full reference is on the [developer page](page:developers).
In this article
How do you create and manage an API key?
Every API call uses a key. There are two kinds: a live key (starts with prk_live_) places real orders that are charged and produced, while a test key (starts with prk_test_) works in the sandbox: orders are created, but nothing is charged or produced.
Open the tab
In your dashboard, go to API keys.
Name the key
Enter a recognisable name under Key name, for example the name of your store or server. That way you know later which key is used where.
Choose test or live
Tick Test key for a sandbox key (
prk_test_). Leave it unticked for a live key (prk_live_). Then choose under Permissions (scopes) what the key may do; if you leave everything ticked, it has all permissions.Create the key
Click Create key. The full key appears once, with the message Copy this key now. You will only see it once.
Copy and store it
Click Copy and put the key somewhere safe right away, such as an environment variable on your server or a password manager. After that, the list only shows the start of the key and when it was Last used.
The API supports permissions (scopes) per key. A key without scopes has all permissions. In the dashboard you choose them under Permissions (scopes) when you create the key. If you leave everything ticked, the key gets all permissions, including permissions added later. The list shows the permissions of each key. To change the permissions of a key, create a new key and revoke the old one.
| Scope | What the key may do |
|---|---|
catalog:read | Read the catalogue and product options (GET /products). |
quotes:write | Calculate prices (POST /quotes). |
orders:read | Read orders and their status (GET /orders). |
orders:write | Place and cancel orders (POST /orders, POST /orders/{id}/cancel). |
reseller-products:read | Read your own products. |
reseller-products:write | Create, update and delete your own products. |
wallet:read | Read your balance and transactions (GET /wallet). |
webhooks:read | Read your webhook endpoints. |
webhooks:write | Add and delete webhook endpoints. |
| No scopes | Everything above. |
Base URL, authentication and limits
- Base URL:
https://www.printen.eu/api/reseller/v1. All paths in this article are relative to it. - Authentication: send the header
Authorization: Bearer prk_live_...orAuthorization: Bearer prk_test_...with every request. - Format: requests and responses are JSON. A successful response is always wrapped in a
datafield; lists also includenextCursorto fetch the next page. - Amounts: always in euro cents.
2873means €28.73. - Limit: up to 120 requests per minute per key. Quotes (
POST /quotes) and new orders also have their own per-account limit, which is lower for new accounts. Every response carriesRateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds) for the limit that runs out first. Above that you get status 429 with aRetry-Afterheader. - Specification: the full OpenAPI specification is at https://www.printen.eu/reseller-api/openapi.json. You can import it into Postman, Insomnia or a code generator.
Test your key with a simple call, for example reading your balance. If you get JSON back with data, your key works.
curl https://www.printen.eu/api/reseller/v1/wallet \
-H "Authorization: Bearer prk_test_..."How do you fetch the catalogue and options?
Use GET /products to search the catalogue. Use q as the search term, category for a category, lang for the language of names and options (nl, en, de or fr), and limit (up to 100) and cursor to page through the results.
curl "https://www.printen.eu/api/reseller/v1/products?q=poster&lang=nl&limit=5" \
-H "Authorization: Bearer prk_test_..."With the id from that list you fetch a single product with all its options and file requirements.
curl "https://www.printen.eu/api/reseller/v1/products/PRODUCT_ID?lang=nl" \
-H "Authorization: Bearer prk_test_..."{
"data": {
"id": "PRODUCT_ID",
"slug": "poster",
"name": "Poster",
"options": [
{
"key": "size",
"label": "Formaat",
"type": "select",
"values": [
{ "value": "A3", "label": "A3" },
{ "value": "A2", "label": "A2" }
]
}
],
"quantities": [1, 5, 10],
"fileRequirements": {
"required": true,
"formats": ["pdf", "png", "jpg"],
"minDpi": 300,
"colorProfile": "CMYK",
"bleedMm": 3,
"fileUrlMustBeHttps": true
}
}
}| Field | Meaning |
|---|---|
options | The product’s choices. Use key as the name and a value from values as the value in your quote, order or own product. |
quantities | The quantities you can order, if the product has fixed quantities. |
fileRequirements | File requirements: allowed formats, minimum resolution (minDpi), colour profile, bleed in mm (bleedMm) and whether the file URL must be https. |
Your first quote: what does an order cost?
Use POST /quotes to price one or more lines for a destination country, including your discount, shipping and VAT. Nothing is ordered or charged. A line contains a resellerSku (your own product) or a productId with options, plus a quantity.
curl -X POST https://www.printen.eu/api/reseller/v1/quotes \
-H "Authorization: Bearer prk_test_..." -H "Content-Type: application/json" \
-d '{"country":"NL","lines":[{"resellerSku":"POSTER-A2","quantity":1}]}'{
"data": {
"lines": [
{
"productId": "PRODUCT_ID",
"productName": "Poster",
"options": { "size": "A2" },
"quantity": 1,
"retailNetCents": 2000,
"netCents": 1800,
"resellerProductId": "RESELLER_PRODUCT_ID"
}
],
"discountPercent": 10,
"subtotalNetCents": 1800,
"shippingNetCents": 574,
"vatRate": 21,
"vatTreatment": "domestic-business",
"vatCents": 499,
"totalCents": 2873,
"currency": "EUR"
}
}| Field | Meaning |
|---|---|
lines[].retailNetCents | The regular consumer price of the line, excluding VAT. |
lines[].netCents | Your price for the line after discount, excluding VAT. |
discountPercent | Your current discount percentage. |
subtotalNetCents | All lines together, excluding VAT. |
shippingNetCents | Shipping to the given country, excluding VAT. |
vatRate | The VAT rate that applies to you, for example 21 or 0. |
vatTreatment | How VAT was determined, for example domestic-business (Dutch VAT) or reverse-charge-eu-b2b (reverse charged). |
vatCents | The VAT amount. |
totalCents | The total charged to your balance or card when you order. |
Place your first order (with an Idempotency-Key)
Choose a unique key
Pick a unique
Idempotency-Keyper order, preferably your own order number (for exampleshop-order-1001). This header is required.Build the lines
Use a
resellerSkuand aquantityper line. If your product has no design yet, send afileUrl(https) as well.Fill in the recipient
Required:
name,address1,postalCode,cityandcountry(ISO-2, for example NL). Optional:company,email,phone,address2andstate.Add your own references
Use
externalOrderIdandexternalOrderNumberto link the order to your own system; they come back in every webhook.referenceis your own reference.Send the request
A new order returns status 201. Check
statein the response:SUBMITTEDmeans paid and on its way to production.
curl -X POST https://www.printen.eu/api/reseller/v1/orders \
-H "Authorization: Bearer prk_test_..." -H "Content-Type: application/json" \
-H "Idempotency-Key: shop-order-1001" \
-d '{"externalOrderId":"1001","lines":[{"resellerSku":"POSTER-A2","quantity":1}],
"recipient":{"name":"Anna de Vries","address1":"Dorpsstraat 1","postalCode":"1234 AB","city":"Utrecht","country":"NL"}}'{
"data": {
"id": "ORDER_ID",
"resellerOrderId": "RESELLER_ORDER_ID",
"orderNumber": "RS-3F9A1C7B2E",
"state": "SUBMITTED",
"totalCents": 2873,
"chargedCents": 0,
"duplicate": false,
"test": true
}
}Why an Idempotency-Key? Networks are unreliable: a timeout does not mean your order did not arrive. An Idempotency-Key lets you repeat a request safely. If you send the same key again, you get the existing order back with "duplicate": true and status 200. A second order is never created.
{
"data": {
"id": "ORDER_ID",
"resellerOrderId": "RESELLER_ORDER_ID",
"orderNumber": "RS-3F9A1C7B2E",
"state": "SUBMITTED",
"totalCents": 2873,
"chargedCents": 0,
"duplicate": true,
"test": true
}
}- Use one key per order, and always the same key when you retry that order.
- An existing key always returns the original order, even if the content of your request is different. If you really want a new order, use a new key.
- A key is at most 200 characters (visible ASCII characters).
- Live keys and test keys each have their own set. A test order with
shop-order-1001therefore does not block a live order with the same key.
Managing your own products through the API
An own product is a catalogue product with fixed options, your SKU, your design file, a title and optionally your retail price. Create one with POST /reseller-products; after that you order it with resellerSku.
curl -X POST https://www.printen.eu/api/reseller/v1/reseller-products \
-H "Authorization: Bearer prk_test_..." -H "Content-Type: application/json" \
-d '{"productId":"PRODUCT_ID","sku":"POSTER-A2-MAT","title":"Poster A2 mat",
"options":{"size":"A2"},"fileUrl":"https://example.com/designs/poster-a2.pdf",
"retailPriceCents":4995,"active":true}'Use PATCH /reseller-products/{id} to change an existing product. You may use the SKU instead of the id. Send only the fields you want to change, for example a new file, a different price or mockup images in mockupUrls.
curl -X PATCH https://www.printen.eu/api/reseller/v1/reseller-products/POSTER-A2-MAT \
-H "Authorization: Bearer prk_test_..." -H "Content-Type: application/json" \
-d '{"mockupUrls":["https://example.com/mockups/poster-a2-living-room.jpg",
"https://example.com/mockups/poster-a2-detail.jpg"],
"retailPriceCents":4495}'productId,skuandtitleare required when creating. Amounts such asretailPriceCentsare in euro cents.- A SKU must be unique within your account. If it already exists, you get 409.
- If the options do not fit this product, you get 422.
- With
"active": falseyou set a product to inactive. Orders with that SKU are then rejected. - Use
DELETE /reseller-products/{id}to delete a product. If it is still linked to a connected store, you get 409.
Which order states are there, and what should you do?
Every order has a state: the status on your side (payment and file). Once the order is SUBMITTED, a production status follows in status.
| state | Meaning | What to do |
|---|---|---|
AWAITING_FUNDS | Your balance is too low. Nothing has been charged. | Top up. If automatic top-up is on, we try that first. After a top-up the order continues by itself; we also retry waiting orders every 15 minutes. |
AWAITING_FILE | A line has no design file. Nothing has been charged. | Cancel the order and place it again with a new Idempotency-Key, with a fileUrl or with a product that does have a file. |
PAYMENT_FAILED | The payment with your saved card failed. | Check your card or save a different one under Balance & payment, then click Retry on the order. |
SUBMITTED | Paid and on its way to production. | Nothing. Follow the production status and tracking through webhooks or GET /orders/{id}. |
CANCELLED | The order has been cancelled and refunded where applicable. | Nothing. |
The production status in status runs from PAID through PROCESSING and PRINTING to SHIPPED and DELIVERED. At SHIPPED you find the tracking code and tracking link in tracking. A cancelled order gets CANCELLED or REFUNDED.
# Orders that are waiting for balance
curl "https://www.printen.eu/api/reseller/v1/orders?state=AWAITING_FUNDS" \
-H "Authorization: Bearer prk_test_..."
# One order: by id, resellerOrderId or order number
curl https://www.printen.eu/api/reseller/v1/orders/RS-3F9A1C7B2E \
-H "Authorization: Bearer prk_test_..."How do you cancel an order?
Use POST /orders/{id}/cancel to cancel an order, as long as it has not gone into production. For {id} you may use the order id, the resellerOrderId or the order number.
curl -X POST https://www.printen.eu/api/reseller/v1/orders/RS-3F9A1C7B2E/cancel \
-H "Authorization: Bearer prk_test_..."- If it works, you get the order back with
stateCANCELLED. - If the order is already in production, you get 409: it can no longer be cancelled.
- Paid from your balance: the amount goes back to your balance and appears as Refund under Transactions.
- Paid by card: the amount goes back to that card.
- If nothing has been charged yet (for example with
AWAITING_FUNDS), nothing is refunded either.
How do you test safely in the sandbox?
With a test key (prk_test_) you build and test your integration without risk. You use the same base URL and the same calls as live.
| In the sandbox | What happens |
|---|---|
| Placing orders | Orders are created and go to SUBMITTED straight away, with "test": true. |
| Charging | Nothing is taken from your balance or card. |
| Production | Nothing is printed or shipped. |
| Webhooks | You receive order.created and order.submitted, and order.cancelled if you cancel the test order. Production events such as order.shipped do not arrive. |
| Idempotency-Key | Test keys have their own set of keys, separate from live. |
Test everything with prk_test_
Check quotes, orders, repeated requests with the same Idempotency-Key, cancelling and your webhook handling.
Create a live key
Under API keys, create a key without ticking Test key.
Swap the key
Replace the
prk_test_key with theprk_live_key in your configuration. Nothing else changes.Check your payment method
Make sure you have enough balance or a saved card under Balance & payment, otherwise live orders stay on
AWAITING_FUNDSorPAYMENT_FAILED.
What does an error mean?
Every error has the same shape: an error object with a short code and a readable message. Look at the HTTP status first; the message usually tells you exactly what is wrong.
{
"error": {
"code": "...",
"message": "..."
}
}| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid request, for example a missing required field or a missing Idempotency-Key on an order. | Read the message and fix your request. |
| 401 | The key is missing, invalid or revoked. | Check the Authorization header and create a new key if needed. |
| 403 | Your account is not (or no longer) approved or is suspended, or the key lacks the right scope. | Check your account in the dashboard or contact us. |
| 404 | The product, order or endpoint does not exist (in your account). | Check the id, SKU or order number. |
| 409 | Conflict, for example: the SKU already exists, the order is already in production and can no longer be cancelled, the product is still linked to a store, or you already have the maximum number of webhook endpoints. | Read the message; do not repeat the request unchanged. |
| 422 | The request is well formed but cannot be carried out, for example an unknown SKU or options that do not fit the product. | Check your SKUs and options. |
| 429 | Too many requests: more than 120 per minute for this key. | Wait the number of seconds in Retry-After. |
| 500 | Something went wrong on our side. | Try again later, for orders with the same Idempotency-Key. If it keeps failing, contact us. |
Related articles
- Webhooks: receive order updates automaticallyReceive order updates on your own URL, verify the signature, process events safely and troubleshoot failed deliveries.
- Creating products: from catalogue product to your own SKUChoose a product, fix the options, upload your design, pick a good SKU, work out your margin and test your product with an order.
- Tracking orders, making changes and solving problemsTrack your orders, understand every status, place an order manually, cancel in time and know what to do about payment issues, address errors or damage.
Still stuck?
We are happy to help, in person on working days and usually the same day.