> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inkrelay.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from PrintNode

> Move an existing PrintNode integration to PrintEasy, with the differences in paths, ids, states and options.

PrintEasy keeps the shape of the PrintNode API, so most integrations move over by changing the base URL, the API key and a few ids. This page lists every difference you need to handle.

## Before and after

A print job in PrintNode:

```bash theme={null}
curl -u "PRINTNODE_KEY:" https://api.printnode.com/printjobs \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: invoice-1042" \
  -d '{
    "printerId": 12345,
    "title": "Invoice 1042",
    "contentType": "pdf_base64",
    "content": "JVBERi0...",
    "source": "my-erp",
    "qty": 1,
    "options": { "copies": 1, "paper": "A4" }
  }'
```

The same job in PrintEasy:

```bash theme={null}
curl -u "pe_live_your_key:" https://api.printeasy.adfinite.com/v1/printjobs \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-1042" \
  -d '{
    "printerId": "0192f0aa-...",
    "title": "Invoice 1042",
    "contentType": "pdf_base64",
    "content": "JVBERi0...",
    "source": "my-erp",
    "qty": 1,
    "options": { "copies": 1, "paper": "A4" }
  }'
```

The response is the job object, not a bare integer, so read the id from `id`.

## Authentication and base URL

PrintEasy accepts the same Basic scheme: send the API key as the username with an empty password, or send `Authorization: Bearer pe_live_...`. Keys are created in the dashboard, one per organization, with the scopes `read`, `print` and `admin`.

The base URL is `https://api.printeasy.adfinite.com/v1`. Every path from the PrintNode API you use keeps its name and gains the `/v1` prefix.

## Ids

PrintEasy ids are UUID strings, not integers. Printers, computers and jobs all use them, so change any column or type that stores a PrintNode id as a number. `POST /v1/printjobs` returns the job object (`{ "id": "0192f1a0-...", "state": "queued", ... }`), not a bare integer. Look up the new printer ids with `GET /v1/printers`.

## API differences

| PrintNode | PrintEasy |
| - | - |
| Base `https://api.printnode.com` | `https://api.printeasy.adfinite.com/v1` |
| `Authorization: Basic base64(KEY:)` | Same header works; `Authorization: Bearer pe_live_...` also accepted |
| `X-Idempotency-Key` (duplicate returns 409) | `Idempotency-Key` (same key and body returns the first job; same key with another body returns 422) |
| `X-Child-Account-By-Id/Email/CreatorRef` | Not supported in v1 (integrator accounts are out of scope) |
| Integer ids, comma id sets `/printers/1,3` | String ids (UUIDv7), one id per path |
| `limit`, `after`, `dir`, plus `Records-*` headers | Same query names (`limit` 1 to 100, default 25; `after` is an id; `dir` is `asc` or `desc`). The body is `{ data, hasMore, nextCursor }`; pass `nextCursor` as the next `after`. No `Records-*` headers |
| Errors `{uid, code, message}`, `Request-Id` | `{error: {code, message, request_id}}`, `X-Request-Id` |
| 10 requests per second per account | 20 per second per API key, `429` with `Retry-After` |
| `GET /whoami`, `/computers[/{id}]`, `/printers[/{id}]`, `/computers/{id}/printers` | Same paths under `/v1` |
| `DELETE /computers/{set}` | `DELETE /v1/computers/{id}` |
| `POST /printjobs` returns a bare integer | `POST /v1/printjobs` returns the job object |
| `DELETE /printjobs/{set}` (cancel) | `POST /v1/printjobs/{id}/cancel` |
| `GET /printjobs/{set}/states` | `GET /v1/printjobs/{id}/events` |
| `POST /webhook`, `PATCH/DELETE /webhook/{id}` | `POST /v1/webhooks`, `PATCH/DELETE /v1/webhooks/{id}` (Standard Webhooks signatures) |
| `GET /download/clients` | `https://downloads.adfinite.com/agent/latest.json` |
| Scales, `/account/*`, `/account/apikey/*` | Not supported; API keys are managed in the dashboard |

## Print jobs and states

| Job field or state | PrintEasy |
| - | - |
| `printerId`, `title`, `source`, `contentType`, `content`, `qty`, `options`, `authentication` | Same names and values. `contentType` is `pdf_uri`, `pdf_base64`, `raw_uri` or `raw_base64`. `printerId` is a UUID string |
| `expireAfter` default 14 days | Same field, in seconds (60 to 604800), default 24 hours |
| Body limit 50 MB | Base64 and URI content up to 25 MB decoded |
| `new` / `sent_to_client` / `done` / `error` / `expired` | `queued` / `sent`, then `received`, `printing` / `done` (spooler finished the job) / `error` / `expired`, plus `cancelled`. Map `new` to `queued` and `sent_to_client` to `sent` |

## Print options

| Option | PrintEasy behaviour |
| - | - |
| `bin`, `copies`, `duplex`, `paper`, `pages`, `rotate`, `fit_to_page`, `color` | All OSes (`rotate` 180 is ignored on Windows) |
| `dpi`, `media`, `collate` | macOS and Linux; ignored with a warning for Windows PDF jobs |
| `nup` | macOS and Linux only |
| Options the printer does not support | Ignored and listed in `stateMessage` |
| Unknown option names | Rejected with `400 invalid_request` |

## Webhooks

PrintNode webhooks send a shared secret header. PrintEasy signs every request with an HMAC following the [Standard Webhooks](https://www.standardwebhooks.com) format (`webhook-id`, `webhook-timestamp`, `webhook-signature`), so verify the signature instead of comparing a header. There are 15 event types (`printjob.queued` through `printjob.expired`, `computer.online`, `computer.offline` and the `printer.*` events), failed deliveries are retried for about 27 hours, and you can replay any delivery from the dashboard. See [Webhooks](/guides/webhooks) for the payload and verification code.

## Rate limit

Each API key may make 20 requests per second. Above that you get `429` with `Retry-After`. See [Rate limits and idempotency](/guides/rate-limits-and-idempotency).

## SDKs

The [TypeScript](/sdks/typescript) and [Python](/sdks/python) SDKs wrap these calls with retries, automatic idempotency keys and `createAndWait`, which replaces polling a job id by hand.

## What you gain

* A full job lifecycle with `received` and `printing` states and an event history for every job.
* Signed webhooks that follow the Standard Webhooks format, with automatic retries and a delivery log you can replay.
* Per-printer default options, so a request only sends what differs from the printer's defaults.
* Scoped API keys (`read`, `print`, `admin`), each limited to one organization.
* Test mode keys (`pe_test_...`) that accept jobs without printing on paper.

## Checklist

1. Install the [agent](/install-agent) on each computer and approve it in the dashboard.
2. Create an API key with the `print` scope and switch the base URL.
3. Replace stored printer ids with the string ids from `GET /v1/printers`.
4. Read the job id from the response object and use `POST /v1/printjobs/{id}/cancel` to cancel.
5. Register a webhook if you relied on PrintNode webhooks, and verify the signature on each delivery.
6. Send an `Idempotency-Key` header instead of `X-Idempotency-Key`, and keep your request rate under 20 per second per key.
7. Test everything with a `pe_test_` key and the virtual printers first (see [Test mode](/guides/test-mode)).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.