Order Documents API

Attach the legal documents issued for an order, such as its invoice and credit notes. Easel doesn’t issue these documents itself. An invoicing integration issues them in the merchant’s invoicing software, then attaches each one to its order here, so the merchant finds it on the order page and the buyer on their order’s tracking page.

Easel keeps only a document’s number, the codes printed beside it, and a link to it. Removing a document from an order leaves the document itself where it was issued.

An order holds at most 50 documents. Every order returns its documents in documents, and attaching, changing, or removing one is recorded in the order’s history.

Order documents have their own scope, order_documents, apart from orders, so an invoicing integration can attach documents with orders:read and order_documents:write, without the right to change orders.

Endpoints


List Order Documents

Retrieve a paginated list of an order’s documents, in the order they were attached.

Endpoint: GET /api/v1/orders/{order_id}/documents/

Scope Required: order_documents:read

Path Parameters

Parameter Type Description
order_id string Order public ID (e.g., ord_d0b6fmv28q6vn14peun0)

Query Parameters

Parameter Type Description
after string Cursor for pagination. Document public ID to return documents after
before string Cursor for pagination. Document public ID to return documents before, for paging back
limit integer Number of documents to return (default 10, max 100)

Every page comes oldest first, whether you page forward with after or back with before. The response reports has_more, which is true when another page lies beyond this one in the direction you’re paging. after and before cannot be used together.

Request

curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/documents/" \
  -H "Authorization: Bearer your_api_key_here"

Response

Status: 200 OK

{
  "data": [
    {
      "id": "odc_d0b6fmv28q6vn14peun0",
      "kind": "invoice",
      "number": "FR 2026/123",
      "authority_code": "JJ3C4K5L-123",
      "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123.pdf",
      "issued_at": "2026-10-01T09:00:00Z",
      "refund_id": null,
      "external_source": "invoicing",
      "external_id": "48211",
      "created_at": "2026-10-01T09:00:04Z",
      "updated_at": "2026-10-01T09:00:04Z"
    },
    {
      "id": "odc_d0b6fmv28q6vn14peun1",
      "kind": "credit_note",
      "number": "NC 2026/7",
      "authority_code": "JJ3C4K5M-7",
      "url": "https://invoices.example.com/d/93bd07/NC-2026-7.pdf",
      "issued_at": "2026-10-04T15:30:00Z",
      "refund_id": "ref_d0b6fmv28q6vn14peun0",
      "external_source": "invoicing",
      "external_id": "48390",
      "created_at": "2026-10-04T15:30:02Z",
      "updated_at": "2026-10-04T15:30:02Z"
    }
  ],
  "has_more": false
}

Error Responses

Invalid query parameter

Status: 400 Bad Request

A malformed after or before, both at once, or an invalid limit returns INVALID_REQUEST_ERROR, with the offending parameter named in the error message:

{
  "error": "Invalid 'limit' parameter: cannot exceed 100",
  "code": "INVALID_REQUEST_ERROR"
}

Order or cursor not found

An order that doesn’t exist, or an after or before that names no document of the order.

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Attach Order Document

Attach a document to an order. Name the document by the system that issued it, in external_source, and its ID there, in external_id. A store can attach a document only once: attaching the same external_source and external_id to the order again returns 409 Conflict with the attached document’s id, so an integration that lost the first response can update that document instead.

Endpoint: POST /api/v1/orders/{order_id}/documents/

Scope Required: order_documents:write

Path Parameters

Parameter Type Description
order_id string Order public ID (e.g., ord_d0b6fmv28q6vn14peun0)

Request Body

{
  "kind": "credit_note",
  "number": "NC 2026/7",
  "authority_code": "JJ3C4K5M-7",
  "url": "https://invoices.example.com/d/93bd07/NC-2026-7.pdf",
  "issued_at": "2026-10-04T15:30:00Z",
  "refund_id": "ref_d0b6fmv28q6vn14peun0",
  "external_source": "invoicing",
  "external_id": "48390"
}

Fields

Field Type Required Description
kind string Yes invoice, credit_note, debit_note, receipt, or delivery_note (see Document Kinds)
number string Yes The document’s legal number as printed, such as FR 2026/123: 1 to 100 printable characters
authority_code string|null No A code a tax authority’s rules print beside the number, such as Portugal’s ATCUD: up to 128 printable characters. Omit it, or send null or "", for none
url string Yes Link the buyer opens: an absolute https URL with a host and no user name or password, of printable ASCII up to 2048 characters. An international domain goes in its xn-- form
issued_at string Yes When the document was issued, as an RFC 3339 timestamp from 2000 on, and not more than a day ahead
refund_id string|null No The refund a credit_note documents. It must be one of the order’s refunds, and only a credit note may name one
external_source string Yes The system that issued the document, up to 32 lowercase letters, digits, -, or _
external_id string Yes The document’s ID in that system, up to 256 characters

Text fields are trimmed. The URL’s scheme and host are lowercased, and the rest is kept as sent, so a signed link still works. issued_at is stored in UTC to the second.

Request

curl -X POST "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/documents/" \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-12345" \
  -d '{
    "kind": "invoice",
    "number": "FR 2026/123",
    "authority_code": "JJ3C4K5L-123",
    "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123.pdf",
    "issued_at": "2026-10-01T09:00:00Z",
    "external_source": "invoicing",
    "external_id": "48211"
  }'

Optional Headers: - Idempotency-Key: Unique string to prevent duplicate requests (see Idempotency)

Response

Returns the attached document.

Status: 201 Created

{
  "id": "odc_d0b6fmv28q6vn14peun0",
  "kind": "invoice",
  "number": "FR 2026/123",
  "authority_code": "JJ3C4K5L-123",
  "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123.pdf",
  "issued_at": "2026-10-01T09:00:00Z",
  "refund_id": null,
  "external_source": "invoicing",
  "external_id": "48211",
  "created_at": "2026-10-01T09:00:04Z",
  "updated_at": "2026-10-01T09:00:04Z"
}

Error Responses

Invalid JSON body

A body that isn’t JSON, or an issued_at that isn’t an RFC 3339 timestamp.

Status: 400 Bad Request

{
  "error": "Invalid JSON body",
  "code": "INVALID_JSON"
}

Invalid field

A field that is missing or breaks its rule. The message says which:

Field error
kind kind must be invoice, credit_note, debit_note, receipt, or delivery_note
number number must be 1 to 100 printable characters
authority_code authority_code must be at most 128 printable characters
url url must be an absolute https URL with a host and no user name or password, of printable ASCII up to 2048 characters
issued_at issued_at must be a time from 2000 on, and not more than a day ahead
external_source, external_id external_source and external_id must be set together; external_source is up to 32 lowercase letters, digits, '-', or '_', and external_id is up to 256 characters
refund_id on a kind other than credit_note only a credit_note documents a refund
refund_id refund_id is not a refund of this order

Status: 400 Bad Request

{
  "error": "number must be 1 to 100 printable characters",
  "code": "INVALID_FIELD"
}

Document already attached to this order

This order already holds a document with the same external_source and external_id, including one a concurrent request attached first. id names it, so you can update it instead.

Status: 409 Conflict

{
  "error": "A record with this external reference already exists",
  "code": "DUPLICATE",
  "id": "odc_d0b6fmv28q6vn14peun0"
}

Document already attached to another order

Another of the store’s orders holds a document with the same external_source and external_id. There is no id, since that document isn’t this order’s.

Status: 409 Conflict

{
  "error": "Another order already has a document with this external reference",
  "code": "EXTERNAL_REF_TAKEN"
}

Request in flight

A request with the same Idempotency-Key is still running. There is no id; retry after a short delay.

Status: 409 Conflict

{
  "error": "Resource is busy, retry shortly",
  "code": "DUPLICATE"
}

Order already holds 50 documents

Status: 409 Conflict

{
  "error": "An order can have at most 50 documents",
  "code": "CONFLICT"
}

Order not found

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Get Order Document

Retrieve one of an order’s documents.

Endpoint: GET /api/v1/orders/{order_id}/documents/{document_id}/

Scope Required: order_documents:read

Path Parameters

Parameter Type Description
order_id string Order public ID (e.g., ord_d0b6fmv28q6vn14peun0)
document_id string Document public ID (e.g., odc_d0b6fmv28q6vn14peun0)

Request

curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/documents/odc_d0b6fmv28q6vn14peun0/" \
  -H "Authorization: Bearer your_api_key_here"

Response

Status: 200 OK

{
  "id": "odc_d0b6fmv28q6vn14peun0",
  "kind": "invoice",
  "number": "FR 2026/123",
  "authority_code": "JJ3C4K5L-123",
  "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123.pdf",
  "issued_at": "2026-10-01T09:00:00Z",
  "refund_id": null,
  "external_source": "invoicing",
  "external_id": "48211",
  "created_at": "2026-10-01T09:00:04Z",
  "updated_at": "2026-10-01T09:00:04Z"
}

Error Responses

Document not found

A document or order that doesn’t exist, or a document of another order.

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Update Order Document

Replace an attached document’s fields, such as to fix its link. The request takes the same fields as Attach Order Document, except external_source and external_id: they stay as the document was attached, and are ignored if sent. Every field is replaced, so an omitted authority_code or refund_id is cleared.

Sending what the document already holds changes nothing: updated_at stays the same and the order’s history records no change, so an integration can retry freely.

Endpoint: PUT /api/v1/orders/{order_id}/documents/{document_id}/

Scope Required: order_documents:write

Path Parameters

Parameter Type Description
order_id string Order public ID (e.g., ord_d0b6fmv28q6vn14peun0)
document_id string Document public ID (e.g., odc_d0b6fmv28q6vn14peun0)

Request Body

{
  "kind": "invoice",
  "number": "FR 2026/123",
  "authority_code": "JJ3C4K5L-123",
  "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123-v2.pdf",
  "issued_at": "2026-10-01T09:00:00Z"
}
Field Type Required Description
kind string Yes As on attach
number string Yes As on attach
authority_code string|null No As on attach; omitted, null, or "" clears it
url string Yes As on attach
issued_at string Yes As on attach
refund_id string|null No As on attach; omitted or null clears it

Request

curl -X PUT "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/documents/odc_d0b6fmv28q6vn14peun0/" \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-12345" \
  -d '{
    "kind": "invoice",
    "number": "FR 2026/123",
    "authority_code": "JJ3C4K5L-123",
    "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123-v2.pdf",
    "issued_at": "2026-10-01T09:00:00Z"
  }'

Response

Returns the updated document.

Status: 200 OK

{
  "id": "odc_d0b6fmv28q6vn14peun0",
  "kind": "invoice",
  "number": "FR 2026/123",
  "authority_code": "JJ3C4K5L-123",
  "url": "https://invoices.example.com/d/8f2a1c/FR-2026-123-v2.pdf",
  "issued_at": "2026-10-01T09:00:00Z",
  "refund_id": null,
  "external_source": "invoicing",
  "external_id": "48211",
  "created_at": "2026-10-01T09:00:04Z",
  "updated_at": "2026-10-02T11:12:40Z"
}

Error Responses

Invalid JSON body

Status: 400 Bad Request

{
  "error": "Invalid JSON body",
  "code": "INVALID_JSON"
}

Invalid field

The same field errors as on attach, except those for external_source and external_id.

Status: 400 Bad Request

{
  "error": "refund_id is not a refund of this order",
  "code": "INVALID_FIELD"
}

Document not found

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Remove Order Document

Take a document off its order. Only the attachment is removed: the document stays where it was issued, and the order’s history keeps a document_removed event naming it.

Endpoint: DELETE /api/v1/orders/{order_id}/documents/{document_id}/

Scope Required: order_documents:write

Path Parameters

Parameter Type Description
order_id string Order public ID (e.g., ord_d0b6fmv28q6vn14peun0)
document_id string Document public ID (e.g., odc_d0b6fmv28q6vn14peun0)

Request

curl -X DELETE "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/documents/odc_d0b6fmv28q6vn14peun0/" \
  -H "Authorization: Bearer your_api_key_here"

Response

Status: 204 No Content

No response body is returned.

Error Responses

Document not found

Status: 404 Not Found

{
  "error": "Resource not found",
  "code": "NOT_FOUND"
}

Order Document Object

Fields

Field Type Description
id string Document public ID
kind string invoice, credit_note, debit_note, receipt, or delivery_note
number string The document’s legal number as printed
authority_code string|null A code a tax authority’s rules print beside the number, such as Portugal’s ATCUD, or null
url string The https link the buyer opens
issued_at string ISO 8601 timestamp of when the document was issued
refund_id string|null Public ID of the refund a credit note documents, or null
external_source string The system that issued the document. Unlike other resources’ external references, it’s always set
external_id string The document’s ID in that system, always set
created_at string ISO 8601 timestamp of when it was attached
updated_at string ISO 8601 timestamp of its last change

Document Kinds

The kinds follow EU VAT practice:

  • invoice - An invoice, full or simplified, including one that is also a receipt (VAT Directive, Articles 220 to 226b)
  • credit_note - Amends an invoice down, as for a refund (Article 219). It can name the refund it documents in refund_id
  • debit_note - Amends an invoice up, as for a charge added after it was issued
  • receipt - Acknowledges a payment apart from the invoice, as for an invoice paid later
  • delivery_note - Travels with the goods