Naar de inhoud

Door deze site te gebruiken, gaat u akkoord met het gebruik van cookies om uw ervaring te verbeteren en het gebruik te analyseren. Zie ons cookiebeleid.

BlotPrints API

All our products can be ordered through our API. Made for developers and for AI agents.

Introduction

The base URL is:

https://www.blotprints.com/api/v1
  • Requests and responses are JSON (Content-Type: application/json).
  • No API key is needed. Calls are rate limited per IP address (see Rate limits).
  • Dates and times are in ISO 8601, in UTC, like 2026-10-25T12:00:00Z.
  • There’s no test mode: a quote is free and changes nothing, and an order only becomes real once it’s paid.

Example order

Ordering a hardcover book from PDFs that are online.

1. Get a quote

curl -X POST https://www.blotprints.com/api/v1/quotes \
  -H "Content-Type: application/json" \
  -d '{
    "items": [{
      "product": "hardcover",
      "size": "a5-portrait",
      "options": { "cover": "matte", "paper": "matte_standard" },
      "quantity": 1,
      "files": {
        "interior": { "url": "https://example.com/book-interior.pdf" },
        "cover": { "url": "https://example.com/book-cover.pdf" }
      }
    }],
    "shipping": { "country": "NL", "method": "standard" }
  }'

2. Place the order, with the quoteId from the response. If the quote has warnings, add "acceptWarnings": true (see 3. Get a quote).

curl -X POST https://www.blotprints.com/api/v1/orders \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f1c2d3e-my-unique-key" \
  -d '{
    "quoteId": "quo_8hQ2kV7xLm3pR9tY",
    "acceptTerms": true,
    "email": "[email protected]",
    "shippingAddress": {
      "name": "Jane Doe",
      "line1": "Keizersgracht 123",
      "postalCode": "1015 CJ",
      "city": "Amsterdam",
      "country": "NL"
    }
  }'

3. Complete payment: open the paymentUrl from the response in a browser.

Placing an order

1. Choose a product

The products tell you what you can order: per product its sizes, options and prices, and the files we need. Use the names from here (product, size, the option values) in your quote.

All products

GET https://www.blotprints.com/api/v1/products

Everything you can order, with its sizes, options and prices.

{
  "products": [
    {
      "product": "hardcover",
      "name": "Hardcover book",
      "sizes": [
        {
          "size": "a5-portrait",
          "width": 148,
          "height": 210,
          "pages": { "min": 24, "max": 400, "step": 2 },
          "files": {
            "interior": { "fileTypes": ["pdf"], "width": 154, "height": 216, "bleed": 3 },
            "cover": { "fileTypes": ["pdf", "jpg", "png"], "width": 354, "height": 255, "bleed": 19 }
          },
          "price": { "base": 15.99, "perExtraPage": 0.40 },
          "options": {
            "cover": {
              "values": [
                { "value": "matte", "name": "Hardcover matte", "price": 5.99 },
                { "value": "glossy", "name": "Hardcover glossy", "price": 0 }
              ],
              "default": "matte"
            },
            "paper": {
              "values": [
                { "value": "matte_standard", "name": "Standard matte", "price": 0, "perExtraPage": 0 },
                { "value": "matte_heavy", "name": "Heavy matte", "price": 1.44, "perExtraPage": 0.07 }
              ],
              "default": "matte_standard"
            }
          }
        },
        {
          "size": "a5-landscape",
          "width": 210,
          "height": 148,
          "pages": { "min": 24, "max": 400, "step": 2 },
          "files": {
            "interior": { "fileTypes": ["pdf"], "width": 216, "height": 154, "bleed": 3 },
            "cover": { "fileTypes": ["pdf", "jpg", "png"], "width": 470, "height": 193, "bleed": 19 }
          },
          "price": { "base": 15.99, "perExtraPage": 0.40 },
          "options": {
            "cover": {
              "values": [
                { "value": "matte", "name": "Hardcover matte", "price": 5.99 },
                { "value": "glossy", "name": "Hardcover glossy", "price": 0 }
              ],
              "default": "matte"
            },
            "paper": {
              "values": [
                { "value": "matte_standard", "name": "Standard matte", "price": 0, "perExtraPage": 0 },
                { "value": "matte_heavy", "name": "Heavy matte", "price": 1.44, "perExtraPage": 0.07 }
              ],
              "default": "matte_standard"
            }
          }
        }
      ]
    }
  ]
}
Field Description
product The name to order with, like hardcover.
name The product’s name, to show to a person.
sizes[].size The name to order with. Portrait and landscape are separate sizes: a5-portrait and a5-landscape, 30x40 and 40x30. A square size has one, like 21x21.
sizes[].width The finished width, in millimetres.
sizes[].height The finished height, in millimetres.
sizes[].pages Only for products with pages (books, magazines, calendars): the fewest and most pages, and the step (2 means an even number).
sizes[].files The files to send, by name, with their types and their size in millimetres including the bleed. A book has an interior and usually a cover, a poster or panel has one design.
sizes[].price.base The price of the size, without options. For products with pages, with the fewest pages.
sizes[].price.perExtraPage Only for products with pages: what each page above the fewest adds.
sizes[].options The options of the size (like cover, paper or hanging): its values, and which one is the default. Sizes can differ: an A0 poster has no frame, and prices depend on the size.
sizes[].options.{option}.values[].value The value to order with, like matte.
sizes[].options.{option}.values[].name The value’s name, to show to a person.
sizes[].options.{option}.values[].price What the value adds to the price; 0 when it’s included.
sizes[].options.{option}.values[].perExtraPage Only for values that cost more per page (a book’s paper): what they add for each page above the fewest.

One product

GET https://www.blotprints.com/api/v1/products/{product}

The same as one product in the list.

2. Add your files

Your files are what we print: the design of a poster or panel, or a book’s pages and cover, as PDF, JPG or PNG. Which files a size needs is in its files (see 1. Choose a product). You can send each file in two ways: as a link we download it from, or uploaded to us.

Give a URL we can download, with https. Handy for files that are already online.

{ "url": "https://example.com/book-interior.pdf" }

The link has to work, without logging in, when you get the quote: that’s when we download the file. Links that expire (like presigned storage links) are fine.

Upload a file

For files on your computer (or an agent’s), ask for an upload address and send the file there.

POST https://www.blotprints.com/api/v1/uploads

No body is needed. Response:

{
  "uploadId": "upl_3vN8qR2xKt5mW7yZ",
  "uploadUrl": "https://…",
  "expiresAt": "2026-10-25T14:30:00Z"
}
Field Description
uploadId The upload, to use in your quote once the file is sent.
uploadUrl Where to send the file, with a PUT.
expiresAt Until when uploadUrl works (an hour).

Then send the file:

curl -T book-interior.pdf "<uploadUrl>"

And use the upload in your quote:

{ "uploadId": "upl_3vN8qR2xKt5mW7yZ" }
  • File types: PDF, JPG and PNG, as the product allows. We go by what’s in the file, not its name or extension: anything else is refused (unsupported_file_type).
  • Up to 200 MB per file.
  • An upload we refuse (another type, or too large) is deleted right away: upload the right file again.
  • An upload address works for an hour; an upload is kept for a day.

3. Get a quote

A quote is the step before an order: you send what you want to print, with the files, and we check the files and tell you what it costs, including shipping. Nothing is ordered or paid yet. If it looks good, you place the order with the quote.

POST https://www.blotprints.com/api/v1/quotes
{
  "items": [
    {
      "product": "poster",
      "size": "a3-landscape",
      "options": { "paper": "matte", "hanging": "photo_frame_black" },
      "quantity": 2,
      "files": { "design": { "uploadId": "upl_3vN8qR2xKt5mW7yZ" } }
    }
  ],
  "shipping": { "country": "NL", "method": "standard" }
}
Field Description
items Required. What to print: one or more items, all in one order.
items[].product Required. The product, like hardcover (see 1. Choose a product).
items[].size Required. The size, like a5-portrait or 40x30.
items[].options Optional. Per option of the product a value, like { "cover": "matte" }. An option you leave out gets its default.
items[].quantity Optional. 1 to 50, the default is 1.
items[].files Required. The files, by name, like interior and cover, or design: the ones in files of the size in the product.
items[].files.{name}.url A link to the file. Give this or uploadId: see 2. Add your files.
items[].files.{name}.uploadId An upload of the file. Give this or url: see 2. Add your files.
shipping Required. Where and how to ship.
shipping.country Required. Any EU country, as its two-letter code, like NL, DE or IT.
shipping.method Optional. standard (the default) or express. Express isn’t available for every product: then the quote ships standard, and says so in shipping.note.

Response:

{
  "quoteId": "quo_8hQ2kV7xLm3pR9tY",
  "expiresAt": "2026-10-26T12:00:00Z",
  "previewUrl": "https://www.blotprints.com/preview/quo_8hQ2kV7xLm3pR9tY",
  "items": [
    {
      "product": "poster",
      "size": "a3-landscape",
      "options": { "paper": "matte", "hanging": "photo_frame_black" },
      "quantity": 2,
      "unitPrice": 25.98,
      "price": 51.96,
      "vat": 9.02,
      "warnings": [
        {
          "code": "design_adjusted",
          "file": "design",
          "message": "The design is a different size or shape than the size needs. We zoom it in to fit, so 9.1% is cut off at the edges. Check the preview.",
          "values": { "cutOffPercent": 9.1 }
        }
      ],
      "previews": [
        {
          "file": "design",
          "url": "https://www.blotprints.com/api/v1/quotes/quo_8hQ2kV7xLm3pR9tY/previews/0-design.jpg",
          "width": 1200,
          "height": 853,
          "description": "The design as it's printed. The red line is where it's cut."
        }
      ]
    }
  ],
  "shipping": {
    "method": "standard",
    "price": 5.00,
    "vat": 0.87,
    "estimatedDelivery": { "from": "2026-10-28", "to": "2026-10-29" },
    "note": null
  },
  "shippingOptions": [
    {
      "method": "standard",
      "price": 5.00,
      "vat": 0.87,
      "estimatedDelivery": { "from": "2026-10-28", "to": "2026-10-29" }
    },
    {
      "method": "express",
      "price": 12.50,
      "vat": 2.17,
      "estimatedDelivery": { "from": "2026-10-27", "to": "2026-10-27" }
    }
  ],
  "total": 56.96,
  "vat": 9.89,
  "currency": "EUR",
  "termsUrl": "https://www.blotprints.com/terms"
}
Field Description
quoteId The quote, to place the order with.
expiresAt Until when the quote can be ordered (24 hours).
previewUrl A page that shows the files as we print them, the same preview as on our website: where it’s cut, the spine of a book, every page. Show it to the person before ordering. It’s in their language, and only whoever has the link can see it.
items[].pageCount Only for products with pages: the pages we counted in your file, which the price is based on.
items[].unitPrice The price of one piece, with its options.
items[].price The price of all pieces of the item.
items[].vat The VAT included in the item’s price.
items[].warnings Things we’ll fix while printing, that you should know about, like a design that’s a different shape and gets cut off a bit. Empty when there are none. To order anyway, set acceptWarnings when you place the order.
items[].warnings[].code What kind of warning, like design_adjusted, pages_adjusted or cover_adjusted (a file of another size, zoomed in to fit), or low_resolution (an image that may print blurry). There are more: show the message to the person.
items[].warnings[].file Which file of the item it’s about.
items[].warnings[].message What’s going on, to show to a person.
items[].warnings[].values The numbers in the message, so you can say it your own way. cutOffPercent on design_adjusted, pages_adjusted, cover_adjusted and scaled: how much is cut off (for pages, the most of any page), with difference: what differs (landscape or portrait: it lies the other way; wider or taller: another shape; smaller or larger: another size, then also fileSize and printSize, like "210 × 297 mm"). dpi and minimumDpi on low_resolution: what the image prints at, and from what it’s sharp. Other warnings can have others, like pageCount.
items[].previews A picture of each file as we print it: a JPG of 1200 pixels on its long side, with the red line where it’s cut. For a book: the whole cover with its spine, and pages 2 and 3 of the interior (the previewUrl shows every page). Empty when we couldn’t make them; the quote works without.
items[].previews[].file Which file of the item it shows.
items[].previews[].url The picture. Only whoever has the link can see it.
items[].previews[].description What the picture shows, and what its lines mean.
shipping.method How it ships: standard or express. Standard when you asked for express and that isn’t available (see note).
shipping.price The shipping costs. Standard shipping is free (0) when the items together cost €50 or more; express keeps its price.
shipping.vat The VAT included in the shipping costs.
shipping.estimatedDelivery When it should arrive, as a range of dates. Not guaranteed.
shipping.note Why it ships another way than you asked, to show to the person: express isn’t available for this order, so it ships standard. Otherwise null.
shippingOptions Every way this order can ship to this country, with its method, price, vat and estimatedDelivery, the one of this quote too. To let the person choose: show these, and get a new quote with the shipping.method they pick. total and vat are for the quote’s own shipping.
total Everything together: the items and shipping.
vat The VAT included in the total.
termsUrl Our terms, which apply to the order. Read them before you accept them when you place the order.

A file we can’t print (wrong type, too few pages, can’t be read) is an error: the quote fails with 422 and lists every problem of every file in problems, so you can fix them all at once. See Errors.

A quote can be ordered once. For another order, get a new quote.

4. Place the order

An order is a quote you’ve decided to buy, with the address to ship it to. You get a payment link: once it’s paid, we print and ship it, and you can follow its status until it arrives.

POST https://www.blotprints.com/api/v1/orders
{
  "quoteId": "quo_8hQ2kV7xLm3pR9tY",
  "acceptTerms": true,
  "acceptWarnings": true,
  "reference": "SHOP-2026-0042",
  "email": "[email protected]",
  "shippingAddress": {
    "name": "Jane Doe",
    "line1": "Keizersgracht 123",
    "line2": "",
    "postalCode": "1015 CJ",
    "city": "Amsterdam",
    "country": "NL"
  }
}
Field Description
quoteId Required. The quote to order, from 3. Get a quote. A quote can be ordered once.
reference Optional. Your own reference for the order, like your order number: up to 100 characters. We return it with the order, so you can match it to yours.
acceptTerms Required. true to accept our terms (the termsUrl of the quote). Without it, the order is refused with terms_not_accepted. The person who pays also accepts them on the payment page.
acceptWarnings Optional. true to order a quote with warnings. Without it, such a quote is refused with warnings_not_accepted.
email Required. Where we send the confirmation and the track and trace.
shippingAddress Required. Where to ship.
shippingAddress.name Required. The name of the receiver.
shippingAddress.line1 Required. The first address line: usually street and house number.
shippingAddress.line2 Optional. A second address line, like an apartment or a company name.
shippingAddress.postalCode Required. The postal code.
shippingAddress.city Required. The city.
shippingAddress.country Required. The country of the quote.
Header Description
Idempotency-Key Optional, but advised. Any unique text, like a UUID, so a retry doesn’t order twice. See Idempotency.

Response (201):

{
  "orderId": "1042-x7Kq2mV9pL4tR8wN3bY6zA",
  "reference": "SHOP-2026-0042",
  "status": "awaiting_payment",
  "paymentUrl": "https://www.blotprints.com/pay/1042-x7Kq2mV9pL4tR8wN3bY6zA",
  "total": 56.96,
  "currency": "EUR"
}
Field Description
orderId The order. Keep it: it’s the only way to look the order up, and it’s secret, as anyone with it can see the order.
reference Your own reference, as you sent it; null without one.
status awaiting_payment: placed, waiting for payment. See 6. Track the order for all statuses.
paymentUrl The page where a person pays for the order. See 5. Complete payment.

5. Complete payment

Open the paymentUrl in a browser. It shows the order and asks for payment (iDEAL, card, and the other methods of the country). Once it’s paid, we print. An order that isn’t paid within 7 days is cancelled.

6. Track the order

GET https://www.blotprints.com/api/v1/orders/{orderId}
{
  "orderId": "1042-x7Kq2mV9pL4tR8wN3bY6zA",
  "reference": "SHOP-2026-0042",
  "status": "shipped",
  "createdAt": "2026-10-25T12:00:00Z",
  "paidAt": "2026-10-25T12:05:00Z",
  "items": [
    {
      "product": "poster",
      "size": "a3-landscape",
      "options": { "paper": "matte", "hanging": "photo_frame_black" },
      "quantity": 2,
      "unitPrice": 25.98,
      "price": 51.96,
      "vat": 9.02
    }
  ],
  "shipping": {
    "method": "standard",
    "price": 5.00,
    "vat": 0.87,
    "trackingUrl": "https://…"
  },
  "shippingAddress": {
    "name": "Jane Doe",
    "line1": "Keizersgracht 123",
    "line2": "",
    "postalCode": "1015 CJ",
    "city": "Amsterdam",
    "country": "NL"
  },
  "total": 56.96,
  "vat": 9.89,
  "currency": "EUR",
  "paymentUrl": null
}
Field Description
status Where the order is, see below.
items, shipping, total, vat As in the quote the order was made from.
paidAt When it was paid; null until then.
shipping.trackingUrl Where to follow the parcel; null until it’s available. It can come a while after the order is marked as shipped.
shippingAddress Where it’s shipped to, as you sent it when you placed the order, tidied up (like the spacing of the postal code).
paymentUrl Where to pay; null once it’s paid or cancelled.

status is one of:

Status Meaning
awaiting_payment Placed, waiting for payment.
paid Paid.
preparing Being prepared for printing.
in_production Being printed.
shipped On its way.
cancelled Not paid in time, or cancelled.

Reference

Errors

An error has an HTTP status and a body like this:

{
  "error": {
    "code": "file_check_failed",
    "message": "The interior has 18 pages. It needs at least 24. The cover isn't a file type we print.",
    "item": 0,
    "file": "interior",
    "problems": [
      {
        "item": 0,
        "file": "interior",
        "code": "too_few_pages",
        "message": "The interior has 18 pages. It needs at least 24.",
        "values": { "pageCount": 18, "min": 24 }
      },
      {
        "item": 0,
        "file": "cover",
        "code": "unsupported_file_type",
        "message": "The cover isn't a file type we print."
      }
    ]
  }
}
Field Description
error.code What went wrong, as a fixed code to check in your code (see the table below).
error.message What’s wrong and how to fix it, written to be shown to a person or read by an agent.
error.item Only for an error about an item of a quote: which one, counting from 0.
error.file Only for an error about a file: which one of the item, like interior, cover or design. With several problems: the first one’s.
error.problems Only for file_check_failed: every problem, of every file and item, each with its item, file, code (like too_few_pages, pdf_only, file_too_large or file_unreadable), message, and the numbers in it (values).
error.docs The part of these docs about the error.
Status Code When
400 invalid_request The JSON is wrong, or a field is missing.
404 not_found No such product, upload, quote or order.
409 quote_already_ordered The quote has been ordered already. Get a new one for another order.
409 idempotency_key_reused The Idempotency-Key was used before, for a different request. Use a new key.
410 quote_expired The quote is older than 24 hours. Get a new one.
422 file_check_failed Files can’t be printed. problems lists them all.
422 file_unreachable We couldn’t download a file link.
422 country_mismatch shippingAddress.country isn’t the country of the quote.
422 terms_not_accepted Set acceptTerms to accept our terms.
422 warnings_not_accepted The quote has warnings: set acceptWarnings.
429 rate_limited Too many requests. Wait Retry-After seconds.
500 server_error Something went wrong on our side. Try again later.

Idempotency

Networks fail, and scripts and agents try again. To make sure an order is never placed twice, send an Idempotency-Key header with POST /orders: any unique text, like a UUID.

Sending the same key again within 24 hours gives back the same order, instead of a new one. Use a new key for each new order.

Rate limits

Per IP address:

What Limit
All calls 60 per minute
POST /quotes 30 per hour
POST /orders 10 per hour

Over a limit, you get 429 with a Retry-After header: the seconds to wait.

For AI agents

This page is also plain Markdown, at /api/docs.md: read that to learn the API. AI tools can also find it through /llms.txt, with the tips below.

MCP server. AI assistants that support the Model Context Protocol (Claude, ChatGPT, Copilot, …) can add BlotPrints as a connector, at:

https://www.blotprints.com/mcp

No login is needed. The assistant then gets the steps of this page as tools: list_products, get_product, create_upload, get_quote, place_order and get_order.

OpenAPI. The API is also described as OpenAPI 3.1, at /api/openapi.json: for tools that read it, like code generators or a custom GPT’s actions.

Tips for agents:

  • Start with GET /products to see what’s possible, and pick sizes and options from it: don’t guess names.
  • For files on the user’s computer, use an upload: curl -T file.pdf "<uploadUrl>".
  • Show the user the quote’s preview (previewUrl), price, warnings and terms (termsUrl) before you place the order, and ask whether the preview looks right.
  • Give the user the paymentUrl to pay: you can’t pay yourself.
  • Keep the orderId to check on the order later.

Frequently asked questions

How do I change a quote?

A quote can't be changed. Get a new one with what you want now: a quote is free and only takes a moment. The old one simply expires after 24 hours.

How do I change or replace a file?

Before you place the order: upload or link the new file, and get a new quote with it.

After you placed it, but before it's paid: the paymentUrl opens our checkout, where the order can still be changed. Change next to an item opens it on our website, with its files, to pick another one.

Once it's paid, it can't be changed anymore: we print it specially for you.

How do I change an order, like its address or quantity?

There's no API call to change an order. Until it's paid, the paymentUrl opens our checkout, where the person paying can still change the address and the quantities, change files, or remove items. GET /orders/{orderId} then shows the order as it is.

You can also let it be and place a new order from a new quote: an order that isn't paid is cancelled by itself after 7 days.

Once it's paid, it can't be changed anymore: we print it specially for you.

How do I cancel an order?

Don't pay it: an unpaid order is cancelled by itself after 7 days, and its status becomes cancelled. Nothing is printed or charged.

A paid order can't be cancelled or returned: we print it specially for you (see our terms).

Can I test the API without ordering?

There's no test mode, and it isn't needed: products, uploads and quotes are free and change nothing. A quote checks your files and shows the previews, so you can test everything up to ordering.

You can even place an order: as long as it isn't paid, nothing is printed, and it's cancelled after 7 days.

Who pays, and can I pay by invoice?

Every order is paid on its own paymentUrl, with iDEAL, card or another method of the country: by you, or by your customer if you send them the link. Paying by invoice or monthly isn't possible yet.

An AI agent can't pay: it gives the paymentUrl to the person.

Can I ship straight to my customer?

Yes. Put your customer's address in shippingAddress. The email gets the confirmation and the track and trace: use your customer's, or your own to keep that to yourself.

One order goes to one address. To ship to several addresses, get a quote and place an order per address.

Which countries do you ship to?

To every country of the EU, with tracking: give its two-letter code in shipping.country, like NL, DE or IT. The quote gives the shipping costs and the expected delivery, which depend on how far it goes. Standard shipping is free from €50; express is faster, for the products that have it. The quote's shippingOptions list every way it can ship, with price and dates.

How do I know when an order is paid or shipped?

There are no webhooks yet. Ask for the order with GET /orders/{orderId} now and then, like every few hours: its status says where it is, and shipping.trackingUrl where the parcel is once it's shipped. The email of the order also gets a confirmation and the track and trace.

My quote has warnings. What should I do?

Look at the previewUrl first: it shows how we print the files. A warning means we fix something while printing, like zooming in a design of another shape, so its edges are cut off.

Looks fine? Order with "acceptWarnings": true. If not, fix the file and get a new quote. The warning's values say how much, like cutOffPercent or dpi.

Why was my file refused?

A file we can't print makes the quote fail with file_check_failed, and problems lists every problem with its file, so you can fix them all at once. Often it's the type (a book's interior must be a PDF), the number of pages, or a file over 200 MB. A link we can't download gives file_unreachable: check that it works without logging in.

The sizes, page counts and types each product needs are in its files.

Are the prices the same as on the website?

Yes, and VAT is included. The VAT in it is that of the country it ships to, and the quote shows it per item and for shipping. Prices can change: a quote's prices hold for its 24 hours.

Do I need an API key? What if the limits are too low?

No key is needed. The rate limits are per IP address, enough for most shops and agents. If you need more, let us know.

What do you do with my files?

We only use them to print your order. Only who has the links can see them: the previewUrl, the previews and the orderId can't be guessed. Uploads and quotes are deleted after 7 days, the files of an order 90 days after it's shipped.

Something's wrong with what arrived. Who do I contact?

Let us know with the order number (the part of the orderId before the dash) and the email of the order, and a photo if it helps. We'll sort it out.

Verzending naar de hele EUSnelle leveringGratis verzending vanaf € 50Bestel met je AI-assistent