{
  "openapi": "3.1.0",
  "info": {
    "title": "BlotPrints API",
    "version": "1.0.0",
    "description": "Order prints from BlotPrints: posters, wall art, books, magazines, calendars and gifts, printed in the Netherlands and shipped in the EU. No API key. Choose a product, add your files, get a quote, place the order, and give the person the paymentUrl: you can't pay yourself.",
    "contact": {
      "email": "info@blotprints.com"
    }
  },
  "externalDocs": {
    "description": "The docs, as Markdown",
    "url": "https://www.blotprints.com/api/docs.md"
  },
  "servers": [
    {
      "url": "https://www.blotprints.com/api/v1"
    }
  ],
  "security": [],
  "paths": {
    "/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "Everything you can order",
        "description": "Per product its sizes, options, prices and the files it needs. Use these names in a quote.",
        "responses": {
          "200": {
            "description": "The products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{product}": {
      "get": {
        "operationId": "getProduct",
        "summary": "One product",
        "parameters": [
          {
            "name": "product",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "poster",
                "canvas",
                "aluminium",
                "plexiglass",
                "dibond",
                "paperback",
                "hardcover",
                "photobook",
                "layflat",
                "magazine",
                "calendar",
                "mug",
                "tile",
                "winebox",
                "spiralbook"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The product",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "404": {
            "description": "No such product",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "operationId": "createUpload",
        "summary": "An address to upload a file to",
        "description": "For files on your computer. Send the file to uploadUrl with a PUT (curl -T file.pdf <uploadUrl>), then use the uploadId in a quote. Up to 200 MB; PDF, JPG or PNG.",
        "responses": {
          "200": {
            "description": "The upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "uploadId": {
                      "type": "string"
                    },
                    "uploadUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/quotes": {
      "post": {
        "operationId": "createQuote",
        "summary": "Check the files and get the price",
        "description": "Nothing is ordered yet. Show the person the preview (previewUrl), the total, the warnings and the termsUrl before placing the order. A quote can be ordered for 24 hours, once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items",
                  "shipping"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "What to print: one or more items, all in one order.",
                    "items": {
                      "type": "object",
                      "required": [
                        "product",
                        "size",
                        "files"
                      ],
                      "properties": {
                        "product": {
                          "type": "string",
                          "enum": [
                            "poster",
                            "canvas",
                            "aluminium",
                            "plexiglass",
                            "dibond",
                            "paperback",
                            "hardcover",
                            "photobook",
                            "layflat",
                            "magazine",
                            "calendar",
                            "mug",
                            "tile",
                            "winebox",
                            "spiralbook"
                          ],
                          "description": "The product, from GET /products."
                        },
                        "size": {
                          "type": "string",
                          "description": "A size of the product, like a5-portrait or 40x30, from GET /products."
                        },
                        "options": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          },
                          "description": "Per option of the size a value, like { \"cover\": \"matte\" }. One left out gets its default."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 50,
                          "default": 1
                        },
                        "files": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "object",
                            "description": "A file: a link we download it from (url), or an upload (uploadId). Give one of them.",
                            "properties": {
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "An https link we can download without logging in, when you get the quote."
                              },
                              "uploadId": {
                                "type": "string",
                                "description": "An upload from POST /uploads, once the file is sent."
                              }
                            }
                          },
                          "description": "The files, by name (interior, cover, design): the ones in files of the size."
                        }
                      }
                    }
                  },
                  "shipping": {
                    "type": "object",
                    "required": [
                      "country"
                    ],
                    "properties": {
                      "country": {
                        "type": "string",
                        "enum": [
                          "AT",
                          "BE",
                          "BG",
                          "CY",
                          "CZ",
                          "DE",
                          "DK",
                          "EE",
                          "ES",
                          "FI",
                          "FR",
                          "GR",
                          "HR",
                          "HU",
                          "IE",
                          "IT",
                          "LT",
                          "LU",
                          "LV",
                          "MT",
                          "NL",
                          "PL",
                          "PT",
                          "RO",
                          "SE",
                          "SI",
                          "SK"
                        ]
                      },
                      "method": {
                        "type": "string",
                        "enum": [
                          "standard",
                          "express"
                        ],
                        "default": "standard"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "description": "A field is wrong or missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such product or upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A file can't be printed or downloaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders": {
      "post": {
        "operationId": "placeOrder",
        "summary": "Order a quote",
        "description": "Returns the paymentUrl: a person opens it to pay. Once paid, we print and ship it.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Any unique text, like a UUID, so a retry doesn't order twice.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "quoteId",
                  "acceptTerms",
                  "email",
                  "shippingAddress"
                ],
                "properties": {
                  "quoteId": {
                    "type": "string",
                    "description": "The quote to order. A quote can be ordered once."
                  },
                  "acceptTerms": {
                    "type": "boolean",
                    "description": "true to accept our terms (the termsUrl of the quote). Show them to the person first."
                  },
                  "acceptWarnings": {
                    "type": "boolean",
                    "description": "true to order a quote with warnings. Show the person the warnings first."
                  },
                  "reference": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Your own reference for the order, like your order number."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Where we send the confirmation and the track and trace."
                  },
                  "shippingAddress": {
                    "type": "object",
                    "required": [
                      "name",
                      "line1",
                      "postalCode",
                      "city",
                      "country"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "The receiver's name."
                      },
                      "line1": {
                        "type": "string",
                        "description": "Usually street and house number."
                      },
                      "line2": {
                        "type": "string",
                        "description": "Like an apartment or a company name."
                      },
                      "postalCode": {
                        "type": "string"
                      },
                      "city": {
                        "type": "string"
                      },
                      "country": {
                        "type": "string",
                        "enum": [
                          "AT",
                          "BE",
                          "BG",
                          "CY",
                          "CZ",
                          "DE",
                          "DK",
                          "EE",
                          "ES",
                          "FI",
                          "FR",
                          "GR",
                          "HR",
                          "HU",
                          "IE",
                          "IT",
                          "LT",
                          "LU",
                          "LV",
                          "MT",
                          "NL",
                          "PL",
                          "PT",
                          "RO",
                          "SE",
                          "SI",
                          "SK"
                        ],
                        "description": "The country of the quote."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The order, waiting for payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orderId": {
                      "type": "string",
                      "description": "Keep it: the only way to look the order up. Secret."
                    },
                    "reference": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "awaiting_payment",
                        "paid",
                        "preparing",
                        "in_production",
                        "shipped",
                        "cancelled"
                      ]
                    },
                    "paymentUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "total": {
                      "type": "number",
                      "description": "In euros, VAT included."
                    },
                    "currency": {
                      "type": "string",
                      "const": "EUR"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is wrong or missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Ordered already, or the key was used for another request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The quote has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Terms or warnings not accepted, or another country",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{orderId}": {
      "get": {
        "operationId": "getOrder",
        "summary": "An order and its status",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orderId": {
                      "type": "string"
                    },
                    "reference": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "awaiting_payment",
                        "paid",
                        "preparing",
                        "in_production",
                        "shipped",
                        "cancelled"
                      ]
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "paidAt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "product": {
                            "type": "string"
                          },
                          "size": {
                            "type": "string"
                          },
                          "options": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "string"
                            }
                          },
                          "quantity": {
                            "type": "integer"
                          },
                          "pageCount": {
                            "type": "integer",
                            "description": "For products with pages: the pages we counted."
                          },
                          "unitPrice": {
                            "type": "number",
                            "description": "In euros, VAT included."
                          },
                          "price": {
                            "type": "number",
                            "description": "In euros, VAT included."
                          },
                          "vat": {
                            "type": "number",
                            "description": "In euros, VAT included."
                          }
                        }
                      }
                    },
                    "shipping": {
                      "type": "object",
                      "properties": {
                        "method": {
                          "type": "string"
                        },
                        "price": {
                          "type": "number",
                          "description": "In euros, VAT included."
                        },
                        "vat": {
                          "type": "number",
                          "description": "In euros, VAT included."
                        },
                        "trackingUrl": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    "shippingAddress": {
                      "type": "object",
                      "required": [
                        "name",
                        "line1",
                        "postalCode",
                        "city",
                        "country"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "The receiver's name."
                        },
                        "line1": {
                          "type": "string",
                          "description": "Usually street and house number."
                        },
                        "line2": {
                          "type": "string",
                          "description": "Like an apartment or a company name."
                        },
                        "postalCode": {
                          "type": "string"
                        },
                        "city": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string",
                          "enum": [
                            "AT",
                            "BE",
                            "BG",
                            "CY",
                            "CZ",
                            "DE",
                            "DK",
                            "EE",
                            "ES",
                            "FI",
                            "FR",
                            "GR",
                            "HR",
                            "HU",
                            "IE",
                            "IT",
                            "LT",
                            "LU",
                            "LV",
                            "MT",
                            "NL",
                            "PL",
                            "PT",
                            "RO",
                            "SE",
                            "SI",
                            "SK"
                          ],
                          "description": "The country of the quote."
                        }
                      }
                    },
                    "total": {
                      "type": "number",
                      "description": "In euros, VAT included."
                    },
                    "vat": {
                      "type": "number",
                      "description": "In euros, VAT included."
                    },
                    "currency": {
                      "type": "string",
                      "const": "EUR"
                    },
                    "paymentUrl": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "null once paid or cancelled."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "not_found",
                  "quote_already_ordered",
                  "idempotency_key_reused",
                  "quote_expired",
                  "file_check_failed",
                  "file_unreachable",
                  "country_mismatch",
                  "terms_not_accepted",
                  "warnings_not_accepted",
                  "rate_limited",
                  "server_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "What's wrong and how to fix it."
              },
              "item": {
                "type": "integer",
                "description": "For an item of a quote: which one, from 0."
              },
              "file": {
                "type": "string",
                "description": "For a file: which one of the item."
              },
              "problems": {
                "type": "array",
                "description": "For file_check_failed: every problem, of every file and item.",
                "items": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "type": "integer"
                    },
                    "file": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "What kind of problem, like too_few_pages, pdf_only, file_too_large or file_unreadable."
                    },
                    "message": {
                      "type": "string"
                    },
                    "values": {
                      "type": "object",
                      "additionalProperties": {
                        "type": [
                          "number",
                          "string"
                        ]
                      }
                    }
                  }
                }
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "The part of the docs about this error."
              }
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "product": {
            "type": "string",
            "description": "The name to order with."
          },
          "name": {
            "type": "string",
            "description": "To show to a person."
          },
          "sizes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "size": {
                  "type": "string"
                },
                "width": {
                  "type": "number",
                  "description": "Finished, in mm."
                },
                "height": {
                  "type": "number",
                  "description": "Finished, in mm."
                },
                "pages": {
                  "type": "object",
                  "description": "Only for products with pages.",
                  "properties": {
                    "min": {
                      "type": "integer"
                    },
                    "max": {
                      "type": "integer"
                    },
                    "step": {
                      "type": "integer"
                    }
                  }
                },
                "files": {
                  "type": "object",
                  "description": "The files to send, by name, with their types and size in mm including the bleed.",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "fileTypes": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "width": {
                        "type": "number"
                      },
                      "height": {
                        "type": "number"
                      },
                      "bleed": {
                        "type": "number"
                      }
                    }
                  }
                },
                "price": {
                  "type": "object",
                  "properties": {
                    "base": {
                      "type": "number",
                      "description": "In euros, VAT included."
                    },
                    "perExtraPage": {
                      "type": "number",
                      "description": "In euros, VAT included."
                    }
                  }
                },
                "options": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "values": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "price": {
                              "type": "number",
                              "description": "In euros, VAT included."
                            },
                            "perExtraPage": {
                              "type": "number",
                              "description": "In euros, VAT included."
                            }
                          }
                        }
                      },
                      "default": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "quoteId": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "previewUrl": {
            "type": "string",
            "format": "uri",
            "description": "A page with the files as we print them. Show it to the person before ordering."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "product": {
                  "type": "string"
                },
                "size": {
                  "type": "string"
                },
                "options": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "quantity": {
                  "type": "integer"
                },
                "pageCount": {
                  "type": "integer",
                  "description": "For products with pages: the pages we counted."
                },
                "unitPrice": {
                  "type": "number",
                  "description": "In euros, VAT included."
                },
                "price": {
                  "type": "number",
                  "description": "In euros, VAT included."
                },
                "vat": {
                  "type": "number",
                  "description": "In euros, VAT included."
                },
                "warnings": {
                  "type": "array",
                  "description": "Things we fix while printing. To order anyway, set acceptWarnings.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "code": {
                        "type": "string",
                        "description": "What kind of warning, like design_adjusted (a file zoomed in to fit, edges cut off) or low_resolution (may print blurry). Show the message to the person."
                      },
                      "file": {
                        "type": "string"
                      },
                      "message": {
                        "type": "string"
                      },
                      "values": {
                        "type": "object",
                        "description": "The numbers in the message, like cutOffPercent, or dpi and minimumDpi.",
                        "additionalProperties": {
                          "type": [
                            "number",
                            "string"
                          ]
                        }
                      }
                    }
                  }
                },
                "previews": {
                  "type": "array",
                  "description": "A picture of each file as we print it (JPG). Empty when we couldn't make them.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "file": {
                        "type": "string"
                      },
                      "url": {
                        "type": "string",
                        "format": "uri"
                      },
                      "width": {
                        "type": "integer"
                      },
                      "height": {
                        "type": "integer"
                      },
                      "description": {
                        "type": "string",
                        "description": "What the picture shows, and what its lines mean."
                      }
                    }
                  }
                }
              }
            }
          },
          "shipping": {
            "type": "object",
            "properties": {
              "method": {
                "type": "string"
              },
              "price": {
                "type": "number",
                "description": "In euros, VAT included."
              },
              "vat": {
                "type": "number",
                "description": "In euros, VAT included."
              },
              "estimatedDelivery": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "format": "date"
                  },
                  "to": {
                    "type": "string",
                    "format": "date"
                  }
                }
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why it ships another way than asked (express isn't available, so standard), or null."
              }
            }
          },
          "shippingOptions": {
            "type": "array",
            "description": "Every way this order can ship, with its price and dates. To choose another, get a new quote with that method.",
            "items": {
              "type": "object",
              "properties": {
                "method": {
                  "type": "string",
                  "enum": [
                    "standard",
                    "express"
                  ]
                },
                "price": {
                  "type": "number",
                  "description": "In euros, VAT included."
                },
                "vat": {
                  "type": "number",
                  "description": "In euros, VAT included."
                },
                "estimatedDelivery": {
                  "type": "object",
                  "properties": {
                    "from": {
                      "type": "string",
                      "format": "date"
                    },
                    "to": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              }
            }
          },
          "total": {
            "type": "number",
            "description": "In euros, VAT included."
          },
          "vat": {
            "type": "number",
            "description": "In euros, VAT included."
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "termsUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}