API reference
The REST API of the print server (tavletta serve), for programs that
print labels: every request with its role, fields, answers and error
codes. A walkthrough for an inventory tool, with code in Python and C#, is
Connecting an inventory tool; installing the server is
Server installation.
This page is made from the OpenAPI 3.1 description of the server
(tools/openapi_docs.py). Programs and code generators read the same
description: docs/openapi.yaml in the source, or
GET /api/v1/openapi.json from a running server.
Basics
- Address:
http://<server>:8790/api/v1, or the HTTPS address of the
reverse proxy. The examples use $TAVLETTA for it and $TOKEN for the
access token:
TAVLETTA=https://labels.example.com/api/v1
TOKEN=tav_0123456789ab_...
- JSON in and out (UTF-8). Requests with a body need
Content-Type: application/json and a Content-Length.
- Every request but
GET /api/v1/health needs the header
Authorization: Bearer <token>.
- Messages come in the language of
Accept-Language (e.g. de-CH),
English by default. Codes never change with the language: programs
check the code, people read the message.
- Unknown fields are refused (
unknown_field): a typo never prints
something else than meant.
Roles
Each access token has a role (tokens.toml, made with
tavletta token new). A role may do what the roles above it may.
| Role |
May |
preview |
list printers and labels, describe labels, previews, printer state, this description |
print |
as preview, and print; see and cancel its own jobs |
admin |
as print, and see and cancel every job |
A token can be limited to some printers of printers.toml: the others
are then "not found" (404 printer_not_found) for it.
Errors
Every error is JSON with a stable code and a message for people:
{"error": {"code": "label_not_found", "message": "No such label: asset-tag.tavletta"}}
| Status |
Code |
Meaning |
| 400 |
bad_request |
The request cannot be read (e.g. a body that ends early). |
| 400 |
not_json |
The body is no valid JSON (415 with another Content-Type). |
| 400 |
invalid_field |
A field has a wrong type or value; the message names it. |
| 400 |
unknown_field |
A field the request does not have (a typo): nothing is done. |
| 400 |
no_label |
Give either label or label_content. |
| 400 |
data_conflict |
Give data_rows or data_file, not both. |
| 400 |
no_rows |
rows selects no data row. |
| 400 |
no_data |
sheet without a data file. |
| 400 |
no_printer |
Several printers and no default: name one in printer. |
| 400 |
no_host |
No printer address (without printers.toml). |
| 400 |
host_given |
host with printers.toml: choose the printer by name. |
| 400 |
too_many_copies |
More copies than the printer's max_copies. |
| 401 |
unauthorized |
Token missing, wrong, expired or revoked. |
| 403 |
forbidden |
The token's role is too low (e.g. preview printing). |
| 403 |
host_not_allowed |
The printer address is not allowed (--allow-printer). |
| 403 |
origin_not_allowed |
A web page of an origin not allowed (--allow-origin). |
| 403 |
header_required |
No token on the server: send X-Tavletta-Request. |
| 404 |
not_found |
No such path. |
| 404 |
label_not_found |
No such label file in the labels folder. |
| 404 |
data_not_found |
No such data file in the labels folder. |
| 404 |
printer_not_found |
No such printer, or one the token may not use. |
| 404 |
job_not_found |
No such job (another token's, or older than 24 hours). |
| 405 |
method_not_allowed |
Not a method of this path. |
| 408 |
timeout |
The request took longer than 30 s to send. |
| 409 |
job_finished |
The job has finished: nothing to cancel. |
| 409 |
counter_not_saved |
The label file is not writable: nothing was printed. |
| 411 |
length_required |
No Content-Length (chunked bodies are not accepted). |
| 413 |
too_large |
Body over --max-body, or too many data rows, columns or cells. |
| 413 |
too_many_labels |
Copies times rows over --max-labels (10000 by default). |
| 413 |
label_too_large |
The label is too large to render (elements, text, pixels). |
| 415 |
not_json |
Send Content-Type: application/json. |
| 421 |
wrong_host |
A loopback server asked for another host name. |
| 422 |
file_problem |
The label file cannot be opened. |
| 422 |
label_not_label |
The file is no Tavletta label. |
| 422 |
label_newer |
The label needs a newer Tavletta: update the server. |
| 422 |
label_damaged |
The label file is damaged. |
| 422 |
label_unprintable |
It would print wrongly: a missing column, a bad code, … |
| 422 |
pages_not_allowed |
Document pages in label_content need the server switch --allow-pdf-pages. |
| 422 |
data_outside |
The label's data file lies outside the labels folder. |
| 422 |
data_problem |
The data file cannot be read, or has no such sheet. |
| 422 |
prepare_failed |
The label cannot be prepared for the printer. |
| 429 |
too_many_requests |
Ten wrong tokens from this address: wait a minute. |
| 429 |
queue_full |
Too many jobs waiting: retry later. |
| 500 |
internal |
An error of the server; it is logged. |
| 502 |
printer_<code> |
The printer is unreachable or reports a problem. |
| 503 |
busy |
All request or render slots taken: retry after a moment. |
printer_<code>: the printer's problem, one of generic, not_found, timeout, stalled, port_closed, busy, paused, cover_open, head_open, jam, media_jam, out_of_labels, out_of_paper, label_not_found, wrong_media, paper_size, page_too_large, too_long, too_wide, code_too_large, ribbon, ribbon_out, ribbon_direct, no_cassette, bad_cassette, supplies, format, resolution, queue_paused, job_held, job_canceled, usb_not_found, usb_permission, serial_permission, battery, overheat, unsupported_model, unknown_model, editor_lite.
A job that fails tells why in its error, with these codes:
| Code |
Meaning |
printer_<code> |
The printer is unreachable or reports a problem (jam, out of labels…). |
counter_not_saved |
Printed, but the next counter value could not be saved. |
label_locked |
Another print held the label file too long. |
prepare_failed |
The label could not be prepared for the printer. |
Limits
| What |
Limit |
| Request body |
4 MB (--max-body) |
| Data rows, columns, cells |
100 000, 1000, 2 000 000 |
| One value or cell |
100 000 characters |
| Copies |
9999, or the printer's max_copies |
| Labels in one job (copies times rows) |
10 000 (--max-labels) |
| Elements of a label |
200, of them 50 images and codes; 5000 characters and 100 lines a text |
| Jobs waiting or printing |
100 in all, 20 per named token |
| Finished jobs kept |
24 hours, at most 1000 |
wait |
300 seconds |
| Requests at once |
32, 8 per client address |
| Wrong tokens |
10 a minute per address, then 429 for a minute |
Requests
Besides the answers listed with each request, every request can get the
general errors of the table above: 401, 403, 405, 408, 421, 429
(too_many_requests), 500 and 503, and one with a body 411, 413
(too_large) and 415.
| Request |
Role |
What |
GET /api/v1/health |
none |
Is the server running; no token needed (for health checks) |
GET /api/v1/openapi.json |
preview |
This description (OpenAPI 3.1); a token of any role |
GET /api/v1/printers |
preview |
The printers of printers.toml the token may use, with their state; without it the printer models, the address set for each and the printers found (--discover) |
GET /api/v1/printers/{id}/status |
preview |
Ask a printer for its state: media, problems, resolution |
GET /api/v1/labels |
preview |
The label files in the labels folder and its subfolders |
GET /api/v1/labels/{name} |
preview |
What a label can be filled in with: elements, variables, data columns, size, counter |
POST /api/v1/preview |
preview |
The label filled in, as an image; nothing is printed |
POST /api/v1/print |
print |
Print: the request is checked, then queued; each printer prints one job at a time |
GET /api/v1/jobs/{id} |
print |
State of a print job (a print token: its own jobs; admin: all). Kept 24 hours after it finished |
DELETE /api/v1/jobs/{id} |
print |
Cancel a job: at once while it waits, after the current label while it prints |
GET /api/v1/health
Is the server running; no token needed (for health checks).
| Status |
Answer |
Codes |
| 200 |
Running |
|
GET /api/v1/openapi.json
This description (OpenAPI 3.1); a token of any role.
| Status |
Answer |
Codes |
| 200 |
OpenAPI |
|
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/openapi.json"
GET /api/v1/printers
The printers of printers.toml the token may use, with their state; without it the printer models, the address set for each and the printers found (--discover).
| Parameter |
In |
Meaning |
X-Tavletta-Request |
header |
Needed (any value) when the server has no token: a web page cannot send it, so it cannot make the server contact printers. |
discover |
query |
Without printers.toml and with --discover: search. default true |
status |
query |
Ask each printer of printers.toml for its state (remembered for 5 s). default true |
| Status |
Answer |
Codes |
| 200 |
Printers |
|
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/printers"
GET /api/v1/printers/{id}/status
Ask a printer for its state: media, problems, resolution.
| Parameter |
In |
Meaning |
X-Tavletta-Request |
header |
Needed (any value) when the server has no token: a web page cannot send it, so it cannot make the server contact printers. |
id |
path |
Printer name (printers.toml) or printer model id. |
host |
query |
Only without printers.toml; needs the role print. |
| Status |
Answer |
Codes |
| 200 |
State |
|
| 400 |
No address, or host given with printers.toml |
no_host, host_given, invalid_field |
| 404 |
Unknown printer, or one the token may not use |
printer_not_found |
| 502 |
The printer cannot be reached or reports a problem |
printer_<code> |
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/printers/it-cab/status"
GET /api/v1/labels
The label files in the labels folder and its subfolders.
| Status |
Answer |
Codes |
| 200 |
Labels |
|
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/labels"
GET /api/v1/labels/{name}
What a label can be filled in with: elements, variables, data columns, size, counter.
| Parameter |
In |
Meaning |
name |
path |
May contain slashes for subfolders; '.tavletta' may be left out. |
| Status |
Answer |
Codes |
| 200 |
Label |
|
| 400 |
Not a name inside the labels folder |
invalid_field |
| 404 |
No such label |
label_not_found |
| 422 |
The file is no label or damaged |
file_problem, label_not_label, label_newer, label_damaged |
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/labels/asset-tag"
POST /api/v1/preview
The label filled in, as an image; nothing is printed.
Body fields:
| Field |
Type |
Meaning |
label |
string |
Name of a label file in the labels folder (see GET /labels), subfolders with '/'; '.tavletta' may be left out. Either label or label_content. |
label_content |
object |
Or the label itself, as the JSON of a .tavletta file, checked like a file that is opened. Its data file is ignored (send data_rows instead), its counter never saved. |
printer |
string |
With printers.toml on the server: the printer's name (default: the default printer the token may use, or the only one). Without it: a printer model id, if not the label's own (the label is fitted as in the app). |
set |
object |
Element name or variable name -> value, as --set on the command line; variables in a value are filled in; 'date' sets the moment {=date} and {=time} start from (ISO 8601). At most 1000 entries. |
set_literal |
object |
Like set, but braces in a value are text, not variables. |
counter_start |
integer |
The {=counter} of the first label; it goes up by the label's step with every label, copy and row. 0 to 999999999999999 |
data_rows |
array of object |
Serial labels: one label per row, column name -> value; the label's {Column} placeholders take the values. Numbers are taken as text, null as an empty cell. At most 100000 rows, 1000 columns, 2000000 cells, 100000 characters a value. |
data_file |
string |
Or a CSV, TSV, ODS or XLSX file in the labels folder. Without data_rows and data_file, the label's own data file is used if it lies beside the label in the labels folder. |
sheet |
string |
Sheet of the data file, the first by default. |
row |
integer |
Data row to show, from 1. 1 to 100000; default 1 |
format |
"png" | "json" |
'json': width, height, warnings and the PNG in base64. default "png" |
view |
"preview" | "printer" |
'printer': the image as the printer gets it (turned, in its colours). default "preview" |
| Status |
Answer |
Codes |
| 200 |
PNG, or JSON with format=json. Warning codes also in the header X-Tavletta-Warnings (comma-separated). |
|
| 400 |
Invalid request |
bad_request, invalid_field, unknown_field, no_label, data_conflict, not_json, no_rows, no_data, no_printer, too_many_copies |
| 404 |
No such label, printer or data file |
label_not_found, printer_not_found, data_not_found |
| 413 |
Too large: body, data rows or the label to render |
too_large, label_too_large |
| 422 |
The label or its data cannot be used |
file_problem, label_not_label, label_newer, label_damaged, label_unprintable, data_outside, data_problem, prepare_failed |
curl -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"label": "asset-tag", "printer": "it-cab", "data_rows": [{"AssetNumber": "100001"}], "format": "png"}' \
-o preview.png \
"$TAVLETTA/preview"
POST /api/v1/print
Print: the request is checked, then queued; each printer prints one job at a time.
Body fields:
| Field |
Type |
Meaning |
label |
string |
Name of a label file in the labels folder (see GET /labels), subfolders with '/'; '.tavletta' may be left out. Either label or label_content. |
label_content |
object |
Or the label itself, as the JSON of a .tavletta file, checked like a file that is opened. Its data file is ignored (send data_rows instead), its counter never saved. |
printer |
string |
With printers.toml on the server: the printer's name (default: the default printer the token may use, or the only one). Without it: a printer model id, if not the label's own (the label is fitted as in the app). |
set |
object |
Element name or variable name -> value, as --set on the command line; variables in a value are filled in; 'date' sets the moment {=date} and {=time} start from (ISO 8601). At most 1000 entries. |
set_literal |
object |
Like set, but braces in a value are text, not variables. |
counter_start |
integer |
The {=counter} of the first label; it goes up by the label's step with every label, copy and row. 0 to 999999999999999 |
data_rows |
array of object |
Serial labels: one label per row, column name -> value; the label's {Column} placeholders take the values. Numbers are taken as text, null as an empty cell. At most 100000 rows, 1000 columns, 2000000 cells, 100000 characters a value. |
data_file |
string |
Or a CSV, TSV, ODS or XLSX file in the labels folder. Without data_rows and data_file, the label's own data file is used if it lies beside the label in the labels folder. |
sheet |
string |
Sheet of the data file, the first by default. |
host |
string |
Printer address, only without printers.toml (default: the one set in the app, else with --discover the only printer found). Refused (host_given) with printers.toml. |
copies |
integer |
Copies of each label (each row); at most the printer's max_copies. Copies times rows: at most --max-labels (10000 by default). 1 to 9999; default 1 |
rows |
string |
The data rows to print, from 1: '3-10', '5', '7-' (to the last) or '-4'. To print again from where a job stopped. |
no_save_counter |
boolean |
Do not save the next {=counter} value into the label file. default false |
wait |
number |
Seconds to wait for the job to finish before answering: 200 if it did, else 202. 0 to 300.0; default 0 |
| Status |
Answer |
Codes |
| 200 |
The job finished within 'wait' |
|
| 202 |
Queued or printing; follow it at Location |
|
| 400 |
Invalid request |
bad_request, invalid_field, unknown_field, no_label, data_conflict, not_json, no_rows, no_data, no_printer, no_host, host_given, too_many_copies |
| 404 |
No such label, printer or data file, or a printer the token may not use |
label_not_found, printer_not_found, data_not_found |
| 409 |
The label file is not writable: the next counter value could not be saved, nothing was printed |
counter_not_saved |
| 413 |
Too large: body, data rows, labels in one job or the label to render |
too_large, too_many_labels, label_too_large |
| 422 |
The label would print wrongly |
file_problem, label_not_label, label_newer, label_damaged, label_unprintable, data_outside, data_problem |
| 429 |
Too many wrong tokens, or too many jobs waiting (100 in all, 20 per named token) |
too_many_requests, queue_full |
curl -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"label": "asset-tag", "printer": "it-cab", "data_rows": [{"AssetNumber": "100001"}], "wait": 30}' \
"$TAVLETTA/print"
GET /api/v1/jobs/{id}
State of a print job (a print token: its own jobs; admin: all). Kept 24 hours after it finished.
| Parameter |
In |
Meaning |
id |
path |
The id of POST /print. |
| Status |
Answer |
Codes |
| 200 |
Job |
|
| 404 |
No such job |
job_not_found |
curl -H "Authorization: Bearer $TOKEN" \
"$TAVLETTA/jobs/$JOB"
DELETE /api/v1/jobs/{id}
Cancel a job: at once while it waits, after the current label while it prints.
| Parameter |
In |
Meaning |
id |
path |
The id of POST /print. |
| Status |
Answer |
Codes |
| 202 |
Canceled, or stopping after the current label |
|
| 404 |
No such job |
job_not_found |
| 409 |
Already finished |
job_finished |
curl -H "Authorization: Bearer $TOKEN" \
-X DELETE \
"$TAVLETTA/jobs/$JOB"
Answers
Job
| Field |
Type |
Meaning |
id |
string |
Random, 16 characters. |
state |
"queued" | "waiting" | "printing" | "done" | "failed" | "canceled" |
waiting: its turn came, another job holds the label file (its counter). done, failed and canceled are final. |
printer |
string |
Printer model id. |
printer_name |
string or null |
Its name in printers.toml. |
host |
string |
The printer's address. |
owner |
string or null |
Name of the token. |
label |
string or null |
Name in the labels folder; null for label_content. |
printed |
integer |
Labels printed so far. |
total |
integer |
Labels to print: copies x rows. |
rows_printed |
integer |
Data rows printed in full: after a failure, print again with rows set to the next one (rows_printed + first row). |
cancel_requested |
boolean |
DELETE was sent. |
error |
null or Warning |
Why it failed: printer_<code>, counter_not_saved, label_locked or prepare_failed. |
remark |
string or null |
A note of the printer, for people. |
warnings |
array of Warning |
Found when it was checked; it printed anyway. |
created |
string |
UTC. |
started |
string or null |
|
finished |
string or null |
|
Label
| Field |
Type |
Meaning |
name |
string |
|
printer |
object |
Model id and name. |
size |
object |
Tape, length, orientation, mm. |
elements |
array of object |
|
variables |
array of string |
Variables of your own, {=lot}: filled in with 'set'. |
builtin_variables |
array of string |
|
columns |
array of string |
Placeholders {Column}: the keys of data_rows. |
data_file |
string or null |
|
data_file_unusable |
boolean |
|
data_sheet |
string or null |
|
counter |
object |
used (the label has {=counter}), next, step. |
features |
array of string |
|
NamedPrinter
A printer of printers.toml.
| Field |
Type |
Meaning |
name |
string |
What requests name in 'printer'. |
printer |
string |
Printer model id. |
model |
string |
Printer model, for people. |
host |
string |
|
description |
string or null |
|
location |
string or null |
|
media |
string or null |
|
default |
boolean |
|
max_copies |
integer |
|
status |
object or null |
The state (as PrinterStatus), or state UNREACHABLE with an error; null with status=false. |
PrinterStatus
| Field |
Type |
Meaning |
printer |
string |
|
host |
string |
|
name |
string |
Its name in printers.toml. |
model |
string |
|
state |
string |
|
error |
null or Warning |
|
dpi |
number |
|
firmware |
string or null |
|
modes |
array of string |
|
media |
object |
What is loaded, as far as the printer tells: tape_mm, remaining_mm, total_mm, pitch_mm, cassette. |
Preview
| Field |
Type |
Meaning |
width |
integer |
|
height |
integer |
|
warnings |
array of Warning |
|
png |
string |
|
Example of a job:
{
"id": "HxIwYqXuV4qYRBM7",
"state": "done",
"printer": "cab",
"printer_name": "it-cab",
"host": "cab-it.example.com",
"owner": "inventory-tool",
"label": "asset-tag.tavletta",
"printed": 20,
"total": 20,
"rows_printed": 20,
"cancel_requested": false,
"error": null,
"remark": null,
"warnings": [],
"created": "2026-11-12T15:34:45+00:00",
"started": "2026-11-12T15:34:45+00:00",
"finished": "2026-11-12T15:35:04+00:00"
}