---
title: 'API brix:papilio'
url: 'https://doc.brix.ch/papilio/api'
markdown: 'https://doc.brix.ch/papilio/api.md'
lang: en
date: '2026-10-26'
description: 'Technical documentation for brix:papilio API – Software developed by brix'
taxonomy:
  category:
    - docs
---

[](#) 

  [ Welcome ](https://doc.brix.ch/)     [ brix:papilio ](https://doc.brix.ch/papilio)      API brix:papilio    

# API brix:papilio

## Introduction

- **REST**ful
- Available at /api/{version}/, e.g. **/api/v1/**subject/method/arg. Current version: v1
- **SSL** is required (except when $env\['custom'\]\['nossl'\] is true - **never** do that in a productive environment)
- [**OAuth 2**](http://oauth.net) is required, endpoints are located under [/oauth](#oauth2)/{endpoint} 
    - Grants: [Authorization Code](#authorization_code) (for humans), [User Credentials](#user_credentials) (for scripts), [Refresh Token](#refresh_token)
    - Clients can be defined by an admin under `/admin/api` and may be restricted to a user
- Output format: **JSON** (default) or XML by adding the parameter *format=xml*, UTF-8
- If an [error](#errors) occures, the response will contain *error* and \_error*description* strings on the top level
- Markup in this document: **required parameter**, {url parameter}, error, user permission
    - Argument notation "name: *type*, description (default)"

## OAuth 2

Every call to an API endpoint not located under /oauth requires the **access\_token** parameter to be set.
You can use [Authorization Code](#authorization_code) + [Access Token](#access_token) (for humans) or [User Credentials](#user_credentials) (for scripts) to get one. Be aware that you might get an invalid\_token error after an hour, in which case you should [refresh](#refresh_token) your token.

### **Authorization Code** \[GET\] */oauth/authorize*

Prompts the user to authorize your client to access papilio with their profile. If the user isn't logged in yet, they will be promted to provide their credentials as well.

#### Arguments

- **client\_id**: *string*, your client id
- **state**: *string*, arbitrary string passed back to you (ensure it matches in the response)

#### Output

Redirects the user to the redirect URI specified in your client's settings, e.g:

*http://my.app.com/authorized?**code**=3f3a8611a369237a7da296a5c8f717789b967b8c&**state**=xyz*

Use this authorization code to get an [access token](#access_token). Or if the user has explicitly denied access you'll get:

\_http://my.app.com/authorized?**error**=access\_denied&error*description=...&**state**=xyz*

### **Access Token** \[POST\] */oauth/token/access*

Request an access token using a previously obtained [authorization code](#authorization_code).

Requires [Basic Auth](https://en.wikipedia.org/wiki/Basic_auth#Client_side) with your client\_id as the username and the client\_secret as the password.

#### Arguments

- **code**: *string*, your authorization code

#### Output

```js
{   "access_token": "5bb2cc6b3c8abfdee1e36d807cffc66346b896cf",
    "expires_in": 3600,
    "token_type": "Bearer",
    "refresh_token": "d872f90159216f56f9224012a3186afaab8fa258"}
```

or invalid\_grant if the authorization code is invalid or has expired

### **User Credentials** \[POST\] */oauth/token/user*

Request an access token directly with a user's username and password. Only recommended for scripts.

Requires [Basic Auth](https://en.wikipedia.org/wiki/Basic_auth#Client_side) with your client\_id as the username and the client\_secret as the password.

#### Arguments

- **username**: *string*, your username
- **password**: *string*, your password

#### Output

```js
{   "access_token": "5bb2cc6b3c8abfdee1e36d807cffc66346b896cf",
    "expires_in": 3600,
    "token_type": "Bearer",
    "refresh_token": "d872f90159216f56f9224012a3186afaab8fa258"}
```

or invalid\_user if the provided credentials are invalid or not allowed to use with this client.

### **Refresh Token** \[POST\] */oauth/token/refresh*

Request a new token if the old one has expired after an hour. Refresh tokens expire after 30 days.

Requires [Basic Auth](https://en.wikipedia.org/wiki/Basic_auth#Client_side) with your client\_id as the username and the client\_secret as the password.

#### Arguments

- **refresh\_token**: *string*, your refresh token from the last authorization

#### Output

```js
{   "access_token": "12d6ba98f806343803c0c446ff0abcfda1302545",
    "expires_in": 3600,
    "token_type": "Bearer"}
```

## Order

### **Get order properties** \[GET\] */order/**{id}***

Get properties of an order. Requires editUsers for orders that don't belong to you.

#### Arguments

- status: *boolean*, expand *status* to named codes, e.g. \["released", "delivered"\] instead of just 288 (false)
- owner: *boolean*, expand *owner* to a user object (false)
- template: *boolean*, expand *template* to a template object (false)
- released\_by: *boolean*, expand released\_by to a user object if present (false)

#### Output

```js
"order": {
    "id": "51038",
    "title": "Inserat Bebbi Jazz",
    "preview": "https:\/\/server.net\/data\/4\/orders\/51038\/live.jpg"
    "status": "288",
    "owner": "1",
    "template": "33",
    "lang": "de", // ISO 639-1
    "quality": "300", // DPI
    "color_space": "cmyk",
    "format": "adsizer",
    "dimension": {
        "unit": "mm",
        "width": "138",
        "height": "210"
    },
    "created": "2013-03-12 16:13",
    "modified": "2013-03-12 16:14",
    "released": "2013-03-12 16:14",
    "delivered": "2013-03-12 16:14",
    "filesize": "12738560", // bytes
    "pages": "1",
    "pin": "37g6m",
    "remark": "This is my demo order",
    "released_by": 8 // controller, if any
    "reject_reason": "typo in headline" // if rejected
    "upload_name": "manually_layouted_file.eps" // if format is _manual_
    "translation_of": "345" // original order of translation if any

}
```

#### Status codes (bitmask)

Note: Status codes should be unset for the next step unless they are marked "sticky"

```js
open             = 1;   // order may be edited, "sticky" until released
precontroll      = 2;   // optional workflow for manual orders before layouting
layout           = 4;   // layouting of a manual (e.g. not automatically renderable) order
control          = 8;   // waiting to be released by a controller
rejected         = 16;  // controller has rejected the order, "sticky" until released
released         = 32;  // controller has released the order, "sticky" forever
released         = 64;  // second controller (two-man rule, optional)
delivery_pending = 128; // order is ready to be downloaded (or "in print")
delivered        = 256; // order has been downloaded (or been mailed), "sticky" forever
charged          = 512; // order was charged (for per-PDF licensing)
exported         = 1024;// charge has been exported to an external system (e.g. SAP)
```

### **Set order properties** \[POST\] */order/**{id}***

Set properties of an order. Requires editUserOrders (or editClients) for orders that don't belong to you. Changing the status manually requires editUsers in any case.

#### Arguments

- title: *string*, title of the order
- status: *int*, [status code(s)](#get_order_properties) (bitmask) of the order. This will overwrite all existing ones.
    Note: *open* should always be set until *released* - see the [order state diagram](https://doc.brix.ch/papilio/api/OrderState.png).
- set\_flag: *int*, turn on a [status flag](#get_order_properties) without overwriting existing ones,
    e.g. if the status is 1 (*open*) and you set\_flag=8 (*control*) the new status is 9 (\[*open*, *control*\])
- unset\_flag: *int*, turn off a [status flag](#get_order_properties) without overwriting existing ones
- delivery: *int*, delivery method (1 = download, 2 = postage)

#### Output

```js
{"success": "true"}
```

### **Delete order** \[DELETE\] */order/**{id}***

Delete an order. Requires editUserOrders (or editClients) for orders that don't belong to you. Note that you can only delete an order that:

- has the *delivered* flag set. This will set the *deleted* flag, meaning that a superadmin can still undelete it.
- **only** has the *open* flag set. This will actually delete the order.

These restrictions apply so that orders don't suddenly disappear from workflows.

#### Output

```js
{"success": "true"}
```

or forbidden if the above conditions are not met.

### **Get a preview PDF** \[GET\] */order/preview/**{id}***

Render a preview PDF (72 DPI) of the order. Requires editUserOrders for those that don't belong to you.

#### Output

An inline application/pdf (or internal\_server\_error if the order could not be rendered)

### **Download order** \[GET\] */order/download/**{id}***

Download an order (usually a PDF). Requires editUsers for orders that don't belong to you.

#### Output

The file is streamed as an attachment with

- Content-Type: application/octet-stream
- Content-Transfer-Encoding: binary

or forbidden if the order isn't downloadable (due to status or ownership)

## Order PIN

Note that a PIN basically makes an order public, so anyone who knows it can download the file.

### **Set PIN for an order** \[POST\] */order/pin/**{id}***

Request a PIN to be created of this order. Requires editUsers for orders that don't belong to you.

#### Output

```js
{"pin": "h58d2", "path": "http://papilio.my.net/pin/"}
```

Combine path + pin to get a "user readable" PIN link.

### **Download by PIN** \[GET\] */order/pin/**{pin}***

Download an order (usually a PDF) that has been made public with a PIN code.

#### Output

Streamed the same way [/order/download](#download_order) is, bad\_request for a malformed PIN or not\_found if there's no such PIN.

### **Delete PIN of an order** \[DELETE\] */order/pin/**{id}***

Delete the PIN of an order. Requires editUsers for orders that don't belong to you.

#### Output

```js
{"success": "true"}
```

## Orders

Tip: You can use the arguments of [/order](#get_order_properties) here as well to get additional information.

### **List your orders** \[GET\] */orders*

List all completed orders of your user (i.e. not *open* and not *deleted*)

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

### **List all orders** \[GET\] */orders/all/{year}*

List the completed orders of all users, limited to a {year} (e.g. 2014, default is current year).
Requires editUsers.

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

### **List deleted orders** \[GET\] */orders/deleted*

List all deleted orders. Requires editClients.

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

### **Search orders** \[GET\] */orders/search/{mode}*

Search for orders with specific properties (with {mode} **and** (default) or **or**). Requires editUsers.

#### Arguments

- title: *string*, (partial) title
- status: *int*, orders having this [status code](#get_order_properties) - combinations are supported, e.g. released + delivered = 288
- status\_not: *int*, orders NOT having this [status code](#get_order_properties)
- template: *int*, template id
- owner: *int*, user id
- lang: *string*, language code (ISO 639-1, e.g. 'de')
- quality: *int*, DPI (most likely 300 or 150)
- color\_space: *enum*, one of: cmyk, rgb, gray
- format: *enum*, one of: standard, manual, adsizer, std\_size
- created: *date*, ISO 8601, i.e. yyyy-mm-dd
- modified: *date*, ISO 8601, i.e. yyyy-mm-dd
- released: *date*, ISO 8601, i.e. yyyy-mm-dd
- delivered: *date*, ISO 8601, i.e. yyyy-mm-dd

Note: *date* types support range search using \_before and/or \_after, e.g. created\_after

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

## Tasks

### **Layoutable orders** \[GET\] */tasks/layoutable*

List orders that need to be layouted by a human. Requires layoutOrders.

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

Use */order/download/{id}* in order to get the automatically rendered PDF on which you should base the manual adaptation.

### **Printable orders** \[GET\] */tasks/printable*

List orders that need to be printed and sent. Requires printOrders.
Note that the orders will contain additional fields:

- recipient: Name (and company, if any) of the recipient
- address: address of the recipient
- delivery\_date: requested date of arrival, if any (ISO 8601)
- copies: number of copies to print

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

Use */order/download/{id}* to get the high end PDF suitable for printing.

### **Translatable orders** \[GET\] */tasks/translatable*

List orders that need to be translated. Requires translateOrders and you will only see orders where the current user can translate from the source language to the destination language.

Note: The *owner* field is used to denote who's working on a translation. If it is **null** the task has not been assigned yet and you can claim it. In order to claim or edit an order, send the user to /translation/edit/{id}

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

### **Completed orders** \[GET\] */tasks/completed/{user}*

List of orders that the user has completed. Requires editUsers for users other than the current one.

#### Output

```js
{"count": 42, "orders": [{"id"; "56789", "title": "some order", ...}, {...}]}
```

## Messages

### **Received messages** \[GET\] */messages/received/{user}*

List of messages that the user (or blank for the current user) has received.

Requires editUsers for users other than yours.

#### Output

```js
{
    "count":42,
    "messages":[
       {
        "id": "2460",
        "title": "some subject",
        "body": "some <strong>text</strong>",
        "sent": true,
        "read": true,
        "timestamp": "2014-10-22 13:37",
        "sender": {"id":"186","first_name":"Joe", ... } // user object
       },
       {...}
    ]
}
```

### **Sent messages** \[GET\] */messages/sent/{user}*

List of messages that the user (or blank for the current user) has sent.

Requires editUsers for users other than yours.

#### Output

```js
{
    "count":42,
    "messages":[
       {
        "id": "2461",
        "title": "some subject",
        "body": "some <strong>text</strong>",
        "sent": true,
        "read": false,
        "timestamp": "2014-10-22 17:37",
        "recipient": {"id":"186","first_name":"Joe", ... } // user object
       },
       {...}
    ]
}
```

### **Mark as read** \[POST\] */messages/read/**{id}***

Marks the message with the {id} as read.

#### Output

```js
{"success": "true"}
```

or forbidden if the message doesn't belong to you

## Template

### **Template** \[GET\] */template/**{id}***

Get information about a specific template.
Notes: To create a new order from a template, simply send the user to /edit/create/{id}

#### Output

```js
{"template": {
    "id": "34",
    "title": "Inserat A4 hoch",
    "preview": "https:\/\/server.net\/data\/4\/preview\/t_34_preview.jpg",
    "dimension": {
        "unit": "mm",
        "width": "210",
        "height": "297"
    },
    "parent": "16", // template group to which it belongs to
    "order": "2", // sort order within the template groups
    "pages": "1",
    "languages": ["de", "fr", "it"],
    "quality": ["150", "300"], // available output DPI
    "color_space": ["gray", "cmyk"],
    "delivery": "download", // download or postage
    "price": "0"
}}
```

or forbidden if you don't have the permission to see the template,
or not\_found if the template doesn't exist or is not active.

## Templates

Notes:

- In order to create a new publication based on a template, send the user to /edit/create/{template id}.
- *order* denotes the global sorting order of both templates and template groups.
- *preview* is the base URL of the preview files, just add \_{size}.jpg ({size}: micro, thumb or preview).
- *examples* indicates that there are example publications (prefilled orders) available. In this case the template should be treated like a template group, the children of which may be retrieved using /templates/examples/{template id} and the user sent to /edit/clone\_from/{order id} instead.

### **Children of a group** \[GET\] */templates/children/{id}*

Get a list of templates and template groups that are children of the template group with this {id}. Omit the group parameter to get all children of the root node. Requires createOrders.

#### Output

```js
{
    "count": 42,
    "children": [
       {
           "id": "8",
           "title": "Some group",
           "status": "2",
           "order": "0",
           "type": "group",
           "multiprev": "0",
           "examples": "0",
           "preview": "/data/4/preview/t_34"
       },
       {
           "id": "10",
           "title": "Some template",
           "status": "2",
           "order": "20",
           "type": "template",
           "multiprev": "0",
           "examples": "0",
           "preview": "/data/4/preview/t_34"
       },
       ...
    ],
    "parents": [ // in that order, for breadcrumbs et al.
        {"id": "1", "title": "top group"},
        {"id": "7", "title": "sub group"} // you are here
    ]
}
```

### **Nested view** \[GET\] */templates/nested*

Get a complete, nested array of all the template groups and their templates. Requires createOrders.

#### Output

```js
{"nested": [
    [
        {
            "id": "8",
            "title": "Top level group",
            "status": "2",
            "order": "0",
            "type": "group",
            "multiprev": "0",
            "examples": "0",
            "preview": "/data/4/preview/g_8"
        },
        [
            [
                {
                    "id": "16",
                    "title": "Sub group",
                    "status": "2",
                    "order": "1",
                    "type": "group",
                    "multiprev": "0",
                    "examples": "0",
                    "preview": "/data/4/preview/g_16"
                },
                [...]
            ],
            {
                "id": "35",
                "title": "Template in top level group",
                "status": "2",
                "order": "19",
                "type": "template",
                "multiprev": "0",
                "examples": "0",
                "preview": "/data/4/preview/t_35"
            }
        ]
    ],
    [...],
    {
        "id": "36",
        "title": "Template on top level",
        "status": "2",
        "order": "20",
        "type": "template",
        "multiprev": "0",
        "examples": "0",
        "preview": "/data/4/preview/t_36"
    }
]}
```

### **Example publications** \[GET\] */templates/examples/**{id}**/{lang}*

Get all the example publications (orders) of the template with this {id}. If no {lang} parameter is present, the current language will be used. Requires createOrders.

#### Output

```js
{"count": 7, "orders": [{"id"; "8739", "title": "some order", ...}, {...}]}
```

### **Search** \[GET\] */templates/search/**{term}***

Search available templates and example publications for a given {term}.
Note that this also searches in template groups and will return its direct children in case of a match.

#### Output

```js
{
    "count": 7,
    "templates":[{"id": "49","title": "some template", ...}, {...}],
    "examples": [{"id"; "8739", "title": "some order", ...}, {...}]
}
```

## Errors

Format:

- Header with the appropriate HTTP status (4xx, 5xx)
- Body: ```js
    {   
    "error": "invalid_token",
    "error_description": "The access token provided has expired"
    }
    ```

General:

- bad\_request, the request is missing a required parameter or contains an invalid value (HTTP 400)
- forbidden, the user does not have the required permissions to perform this action (HTTP 403)
- not\_found, the object you're looking for does not appear to exist (HTTP 404)
- method\_not\_allowed, invalid http-method used, e.g. GET instead of POST (HTTP 405)
- internal\_server\_error, something went wrong internally - check the server logs (HTTP 500)
- not\_implemented, the method that was called does not exist (HTTP 501)

Oauth:

- invalid\_client, this API client does not exist or may not be used in that scope (HTTP 400)
- invalid\_uri, the redirect URI used for interactive OAuth is invalid - check settings (HTTP 400)
- invalid\_grant, the username/password or authorization code provided is invalid (HTTP 400)
- invalid\_request, missing parameter or wrong format (HTTP 400)
- invalid\_token, the access\_token is missing, invalid or has expired (HTTP 401)
- invalid\_user, the user account has expired, isn't active or may not be used with this client (HTTP 403)

Generic HTTP errors may also be thrown by the framework, where the *error* will start with http*error*{number}

 [ ](https://doc.brix.ch/papilio/system) [](https://doc.brix.ch/papilio/release_notes)

---

## Navigation

- Parent: [brix:papilio](https://doc.brix.ch/papilio.md)
- Previous: [System Guide](https://doc.brix.ch/papilio/system.md)
- Next: [Release Notes](https://doc.brix.ch/papilio/release_notes.md)
