Skip to main content
Print products from 1 piece

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.

The API keys tab in your dashboard.
  1. Open the tab

    In your dashboard, go to API keys.

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

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

  4. Create the key

    Click Create key. The full key appears once, with the message Copy this key now. You will only see it once.

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

ScopeWhat the key may do
catalog:readRead the catalogue and product options (GET /products).
quotes:writeCalculate prices (POST /quotes).
orders:readRead orders and their status (GET /orders).
orders:writePlace and cancel orders (POST /orders, POST /orders/{id}/cancel).
reseller-products:readRead your own products.
reseller-products:writeCreate, update and delete your own products.
wallet:readRead your balance and transactions (GET /wallet).
webhooks:readRead your webhook endpoints.
webhooks:writeAdd and delete webhook endpoints.
No scopesEverything 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_... or Authorization: Bearer prk_test_... with every request.
  • Format: requests and responses are JSON. A successful response is always wrapped in a data field; lists also include nextCursor to fetch the next page.
  • Amounts: always in euro cents. 2873 means €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 carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds) for the limit that runs out first. Above that you get status 429 with a Retry-After header.
  • 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.

Test your key
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.

Search products
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.

One product with options
curl "https://www.printen.eu/api/reseller/v1/products/PRODUCT_ID?lang=nl" \
  -H "Authorization: Bearer prk_test_..."
Response (shortened example)
{
  "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
    }
  }
}
FieldMeaning
optionsThe product’s choices. Use key as the name and a value from values as the value in your quote, order or own product.
quantitiesThe quantities you can order, if the product has fixed quantities.
fileRequirementsFile 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.

Request a quote
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}]}'
Response
{
  "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"
  }
}
FieldMeaning
lines[].retailNetCentsThe regular consumer price of the line, excluding VAT.
lines[].netCentsYour price for the line after discount, excluding VAT.
discountPercentYour current discount percentage.
subtotalNetCentsAll lines together, excluding VAT.
shippingNetCentsShipping to the given country, excluding VAT.
vatRateThe VAT rate that applies to you, for example 21 or 0.
vatTreatmentHow VAT was determined, for example domestic-business (Dutch VAT) or reverse-charge-eu-b2b (reverse charged).
vatCentsThe VAT amount.
totalCentsThe total charged to your balance or card when you order.

Place your first order (with an Idempotency-Key)

From request to production.
  1. Choose a unique key

    Pick a unique Idempotency-Key per order, preferably your own order number (for example shop-order-1001). This header is required.

  2. Build the lines

    Use a resellerSku and a quantity per line. If your product has no design yet, send a fileUrl (https) as well.

  3. Fill in the recipient

    Required: name, address1, postalCode, city and country (ISO-2, for example NL). Optional: company, email, phone, address2 and state.

  4. Add your own references

    Use externalOrderId and externalOrderNumber to link the order to your own system; they come back in every webhook. reference is your own reference.

  5. Send the request

    A new order returns status 201. Check state in the response: SUBMITTED means paid and on its way to production.

Place an order
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"}}'
Response (sandbox)
{
  "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.

Repeated request with the same key
{
  "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-1001 therefore 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.

Create a product
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.

Update mockups and price
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, sku and title are required when creating. Amounts such as retailPriceCents are 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": false you 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.

stateMeaningWhat to do
AWAITING_FUNDSYour 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_FILEA 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_FAILEDThe payment with your saved card failed.Check your card or save a different one under Balance & payment, then click Retry on the order.
SUBMITTEDPaid and on its way to production.Nothing. Follow the production status and tracking through webhooks or GET /orders/{id}.
CANCELLEDThe 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.

Read orders
# 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.

Cancel an order
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 state CANCELLED.
  • 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 sandboxWhat happens
Placing ordersOrders are created and go to SUBMITTED straight away, with "test": true.
ChargingNothing is taken from your balance or card.
ProductionNothing is printed or shipped.
WebhooksYou 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-KeyTest keys have their own set of keys, separate from live.
  1. Test everything with prk_test_

    Check quotes, orders, repeated requests with the same Idempotency-Key, cancelling and your webhook handling.

  2. Create a live key

    Under API keys, create a key without ticking Test key.

  3. Swap the key

    Replace the prk_test_ key with the prk_live_ key in your configuration. Nothing else changes.

  4. Check your payment method

    Make sure you have enough balance or a saved card under Balance & payment, otherwise live orders stay on AWAITING_FUNDS or PAYMENT_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.

Shape of an error
{
  "error": {
    "code": "...",
    "message": "..."
  }
}
StatusMeaningWhat to do
400Invalid request, for example a missing required field or a missing Idempotency-Key on an order.Read the message and fix your request.
401The key is missing, invalid or revoked.Check the Authorization header and create a new key if needed.
403Your 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.
404The product, order or endpoint does not exist (in your account).Check the id, SKU or order number.
409Conflict, 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.
422The 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.
429Too many requests: more than 120 per minute for this key.Wait the number of seconds in Retry-After.
500Something went wrong on our side.Try again later, for orders with the same Idempotency-Key. If it keeps failing, contact us.

Still stuck?

We are happy to help, in person on working days and usually the same day.

All articles