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](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 text", "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 text", "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}