Create job
/v20/jobsCreates a print job for the specified printer. The orderData is validated against the printer's assigned JobSchema before the job is created.
Jobs are created in the queued state with a 5-minute expiration window.
Authentication
Requires HTTP Basic authentication with your API Credential ID and Token.
Content Type
application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
printerId | string (UUID) | Yes | The printer to send the job to |
webhook_url | string (https URL) | Yes | URL that receives a callback on every Job state change |
webhook_method | string | Yes | HTTP method for the webhook callback. Must be POST or PUT |
copies | integer (1–3) | No | Number of times to print this job. Default 1. |
print_at | string (ISO 8601 UTC) | No | Schedule the print for a specific time. Default: now (immediate). Max 30 days in the future. |
orderData | object | Yes | Order data matching the printer's JobSchema |
The structure of orderData depends on the JobSchema assigned to the printer. Each schema is a JSON Schema that the order data must conform to, and schemas are configured per vertical (restaurant, florist, Shopify, etc.).
Date-time fields must be valid ISO 8601 timestamps that include a timezone suffix (e.g., 2026-04-15T18:15:00Z or 2026-04-15T13:15:00-05:00). Timestamps without a timezone will fail validation.
Webhooks
webhook_url and webhook_method live at the top level of the request body (alongside printerId and orderData) — they are not part of orderData. Eatabit calls this URL every time the Job's state changes so your system can react to lifecycle events without polling.
| Field | Type | Required | Description |
|---|---|---|---|
webhook_url | string (https URL) | Yes | URL that receives a callback on every Job state change. |
webhook_method | string | Yes | HTTP method for the callback. Must be POST or PUT. |
Delivery contract:
- One attempt per state change. Eatabit does not retry on
4xx/5xxresponses or on network errors. - The request body is a state update: it identifies the Job and describes the state change that just occurred. It is deliberately not the response body of GET /v20/jobs/{jobId} — it carries no
orderData, no other job metadata, and no prior state-change events. See Callback body below. Content-Type: application/vnd.api+json,User-Agent: eatabit.io APIv20.- Each attempt becomes a new event on the Job, visible under
data.relationships.eventsandincludedonGET /v20/jobs/{'{'}jobId{'}'}. The event'smetadatacapturesresponse_status,response_body(truncated), and any network error message.
Callback body:
Every callback carries the same state-update envelope. eventType names the state change the callback is about, and state is the Job's state as of that same change — never a later state that has already superseded it.
{
"data": {
"type": "jobs",
"id": "9af67ab2-ceb6-4dae-9ef3-fe145ae65cd1",
"attributes": {
"eventType": "job.printed",
"state": "printed",
"externalId": "order-1024",
"source": "apiV20",
"environment": "production",
"createdAt": "2026-03-22T12:00:00Z",
"updatedAt": "2026-03-22T12:00:42Z",
"expiresAt": "2026-03-22T12:05:00Z",
"printAt": "2026-03-22T12:00:00Z"
},
"relationships": {
"account": { "data": { "type": "accounts", "id": "c75c40ff-3263-4171-afe4-1e138f8ea616" } },
"printer": { "data": { "type": "printers", "id": "f9d43179-9c10-4502-8f62-892740a65318" } }
}
}
}
| Field | Type | Description |
|---|---|---|
eventType | string | The state change this callback is about (e.g. job.printed) |
state | string | The Job's state as of that change |
externalId | string | Your correlation key — the id from your orderData when you supplied one, otherwise the Job id returned by POST /v20/jobs |
source | string | Always apiV20 |
environment | string | production or development |
createdAt | string (date-time) | Job creation timestamp |
updatedAt | string (date-time) | Last update timestamp |
expiresAt | string (date-time) | Job expiration timestamp |
printAt | string (date-time) | When the Job is scheduled to print — the submission time for an immediate print |
relationships.account | accounts | The account that owns this Job |
relationships.printer | printers | The printer this Job was sent to |
There is no metadata, no included array and no events relationship. Because externalId is your own reference, most integrations can act on a callback without calling back at all; when you do need the order data or the Job's event history, fetch it from GET /v20/jobs/{jobId}.
See Webhooks for the full reference: every eventType a Job can send, which callbacks arrive on each lifecycle path — including how a scheduled Job is announced — and what a receiver has to handle.
Scheduled prints
Set print_at at the root of the request body to defer the print to a specific time. Useful for end-of-day reports, advance order confirmations, or any "print this at X" flow.
- Optional. Default: now (immediate print, today's behavior).
- Allowed values: ISO 8601 timestamp in UTC (must end with
Zor+00:00). Other offsets (e.g.-05:00) are rejected — clients in non-UTC zones must convert before sending. - Past or "now" timestamps print immediately (same as omitting the field).
- Future timestamps create the job in
scheduledstate. The render pipeline runs atprint_at, not at submission time, so the printed receipt always reflects current data. - Maximum 30 days in the future.
- Cancel a scheduled job before it fires with
DELETE /v20/jobs/{'{'}jobId{'}'}(see below). Cancelled jobs never print and webhooks firejob.cancelledonce.
{
"printerId": "f9d43179-9c10-4502-8f62-892740a65318",
"webhook_url": "https://example.com/eatabit/webhook",
"webhook_method": "POST",
"print_at": "2026-04-26T18:30:00Z",
"orderData": { "...": "..." }
}
Copies
Set copies at the root of the request body to print the same job more than once on the same printer. Useful when the operator needs a customer copy + restaurant copy + delivery-driver copy from a single submission.
- Optional. Default
1. Allowed values:1,2, or3. - Each copy is a separate physical print on the same printer; the printed output is identical for every copy.
- Eatabit creates one Job record per request regardless of
copies.GET /v20/jobs/{'{'}jobId{'}'}returns one Job, and webhooks fire once per state transition (not once per copy).
Example Request — Restaurant single order
This example conforms to the default restaurant JobSchema for a single order. All listed fields inside orderData are required.
{
"printerId": "f9d43179-9c10-4502-8f62-892740a65318",
"webhook_url": "https://example.com/eatabit/webhook",
"webhook_method": "POST",
"orderData": {
"orderType": "single",
"id": "38086725",
"restaurant": {
"name": "MD Pizza Factory",
"phone": "800-709-7191",
"address": "4706 N Midkiff Rd, Midland, TX 79705"
},
"delivery_service": {
"name": "delivery.com",
"support_phone": "800-709-7191"
},
"customer": {
"name": "John Smith",
"phone": "432-894-7532"
},
"line_items": [
{
"quantity": 1,
"name": "Pepperoni Pizza",
"category": "pizza",
"price": 18.00,
"subtotal": 18.00,
"options": [
{ "name": "Extra Cheese", "quantity": 1, "price": 2.00 }
]
}
],
"totals": {
"subtotal": 18.00,
"tax": 1.49,
"tip": 5.00,
"delivery_fee": 3.99,
"total": 28.48,
"quantity": 1
},
"delivery_address": {
"street": "123 Main St",
"city": "Springfield",
"state": "PA",
"zip_code": "19000"
},
"delivery_instructions": "Leave at door",
"fulfillment": {
"method": "delivery",
"promised_time": "2026-04-15T18:15:00Z"
},
"payment": {
"method": "Pre-Paid",
"is_prepaid": true
}
}
}
Required fields (restaurant default schema)
Base required fields inside orderData:
| Order type | Required top-level orderData fields |
|---|---|
single | orderType, id, customer, restaurant, line_items, totals, fulfillment, payment |
group | orderType, id, customer, restaurant, totals, fulfillment, payment, children |
Additional fields required when fulfillment.method: "delivery" (single or group):
delivery_servicedelivery_addressdelivery_instructions
Pickup (fulfillment.method: "pickup") and dine-in (fulfillment.method: "dine-in") orders may omit all three.
Other verticals and custom schemas have their own required fields — the 400 validation error response always includes the schema identity so you can correlate.
Example Request — Restaurant pickup order
Pickup orders drop delivery_service, delivery_address, and delivery_instructions. The fulfillment block identifies the order as pickup; the restaurant's own address + phone already print as the footer of the receipt. This example also sets copies: 2 to print one ticket for the customer and one for the kitchen.
{
"printerId": "f9d43179-9c10-4502-8f62-892740a65318",
"webhook_url": "https://example.com/eatabit/webhook",
"webhook_method": "POST",
"copies": 2,
"orderData": {
"orderType": "single",
"id": "38086730",
"restaurant": {
"name": "MD Pizza Factory",
"phone": "800-709-7191",
"address": "4706 N Midkiff Rd, Midland, TX 79705"
},
"customer": {
"name": "John Smith",
"phone": "432-894-7532"
},
"line_items": [
{
"quantity": 1,
"name": "Pepperoni Pizza",
"category": "pizza",
"price": 18.00,
"subtotal": 18.00
}
],
"totals": {
"subtotal": 18.00,
"tax": 1.49,
"total": 19.49,
"quantity": 1
},
"fulfillment": {
"method": "pickup",
"promised_time": "2026-04-24T18:15:00Z",
"pickup_type": "Customer Pickup"
},
"payment": {
"method": "Pre-Paid",
"is_prepaid": true
}
}
}
Response
201 — Job created
Returns the full JSON:API envelope for the newly created job. Shape is identical to GET /v20/jobs/{jobId}; data.relationships.events.data and included are empty arrays since the job has no events yet. Content-Type: application/vnd.api+json.
{
"data": {
"type": "jobs",
"id": "9af67ab2-ceb6-4dae-9ef3-fe145ae65cd1",
"attributes": {
"state": "queued",
"environment": "production",
"externalId": "38086725",
"source": "apiV20",
"createdAt": "2026-04-23T17:12:34.000Z",
"updatedAt": "2026-04-23T17:12:34.000Z",
"expiresAt": "2026-04-23T17:17:34.000Z",
"metadata": {
"orderData": { "...": "..." },
"webhook_url": "https://example.com/eatabit/webhook",
"webhook_method": "POST"
}
},
"relationships": {
"account": { "data": { "type": "accounts", "id": "..." } },
"printer": { "data": { "type": "printers", "id": "..." } },
"events": { "data": [] }
}
},
"included": []
}
Use GET /v20/jobs/{jobId} to check the job's state and view lifecycle events.
All error responses use JSON:API (Content-Type: application/vnd.api+json) with an errors array. Each error has status, title, and detail. Schema-validation errors additionally include a source.pointer (JSON Pointer) per failure and the schema identity in a top-level meta.schema block.
400 — Missing or invalid request body
{
"errors": [
{
"status": "400",
"title": "Bad Request",
"detail": "printerId is required"
}
]
}
{
"errors": [
{
"status": "400",
"title": "Bad Request",
"detail": "orderData is required and must be an object"
}
]
}
400 — Schema validation failed
Returned when orderData does not match the printer's JobSchema. One entry per field failure; source.pointer is a JSON Pointer into orderData.
{
"errors": [
{
"status": "400",
"title": "Validation failed",
"detail": "must be string",
"source": { "pointer": "/customer/name" }
},
{
"status": "400",
"title": "Validation failed",
"detail": "must be number",
"source": { "pointer": "/totals/total" }
}
],
"meta": {
"schema": {
"name": "restaurant-default",
"vertical": "restaurant",
"version": 1
}
}
}
403 — Account not found or disabled
{
"errors": [
{
"status": "403",
"title": "Forbidden",
"detail": "Account not found"
}
]
}
{
"errors": [
{
"status": "403",
"title": "Forbidden",
"detail": "Account is disabled. Contact support to re-enable your account."
}
]
}
404 — Printer not found
Returned when the printer does not exist or belongs to a different account.
{
"errors": [
{
"status": "404",
"title": "Not Found",
"detail": "Printer not found"
}
]
}
422 — Printer not ready
Returned when the printer exists but cannot accept jobs. The detail value identifies the specific reason:
Printer is disabled. Enable the printer before sending jobs.Printer has no device assignedPrinter has no subscription. Start one from the printer's Subscription tab in the dashboard, then send jobs.Printer is not configured for v20 API (missing jobSchemaV20Id)Job schema not found or disabled
{
"errors": [
{
"status": "422",
"title": "Unprocessable Entity",
"detail": "Printer has no subscription. Start one from the printer's Subscription tab in the dashboard, then send jobs."
}
]
}
401 — Not authorized
Returned when the API credential is invalid, disabled, or missing.
500 — Internal server error
{
"errors": [
{
"status": "500",
"title": "Internal Server Error",
"detail": "Failed to create job"
}
]
}