Orders API
Manage orders in your store. An order is a customer purchase, moving
through a few statuses from pending to fulfilled.
Related resources:
- Order Downloads for digital delivery
- Shipments for shipping and tracking
- Refunds for refunds against an order
Endpoints
- List Orders -
GET /api/v1/orders/ - Get Order -
GET /api/v1/orders/{order_id}/ - Update Order -
PUT /api/v1/orders/{order_id}/ - List Order Events -
GET /api/v1/orders/{order_id}/events/
List Orders
Retrieve a paginated list of orders for your store, oldest first.
Endpoint: GET /api/v1/orders/
Scope Required: orders:read
Query Parameters
| Parameter | Type | Description |
|---|---|---|
after |
string | Cursor for pagination. Order public ID to return orders after |
before |
string | Cursor for pagination. Order public ID to return orders before, for paging back |
limit |
integer | Number of orders to return (default 10, max 100) |
status |
string | Filter by order status (pending, paid, fulfilled) |
refund_status |
string | Filter by refund status (none, partially_refunded, refunded) |
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. An invalid limit returns
INVALID_REQUEST_ERROR.
Request
curl -X GET "https://studio.easel.engineering/api/v1/orders/?status=paid" \
-H "Authorization: Bearer your_api_key_here"
Response
Status: 200 OK
{
"data": [
{
"id": "ord_d0b6fmv28q6vn14peun0",
"number": 1001,
"customer_id": "cus_d0b6fmv28q6vn14peun0",
"location_id": null,
"status": "paid",
"refund_status": "none",
"shipping": 599,
"total": 2598,
"discount": 0,
"discount_reason": null,
"billing_zip": "12345",
"phone": "+13105551234",
"shipping_needed": true,
"gift_option": null,
"gift_note": null,
"shipping_rate": {"name": "Ground", "transit": {"min": 3, "max": 5}},
"tax": 0,
"prices_include_tax": false,
"taxes": [],
"gift_card": null,
"amount_charged": 2598,
"items": [],
"downloads": [],
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T10:15:00Z",
"canceled_at": null,
"canceled_by": null
}
],
"has_more": true
}
The list response does not load line items or downloads, so items and
downloads are empty. Fetch a single order to see them.
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"
}
Failed to list orders
Status: 500 Internal Server Error
{
"error": "Internal server error",
"code": "INTERNAL_ERROR"
}
Get Order
Retrieve a single order by its public ID. This response includes the order's
line items and its downloads for digital items.
Endpoint: GET /api/v1/orders/{order_id}/
Scope Required: orders:read
Path Parameters
| Parameter | Type | Description |
|---|---|---|
order_id |
string | Order public ID (e.g., ord_d0b6fmv28q6vn14peun0) |
Request
curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/" \
-H "Authorization: Bearer your_api_key_here"
Response
Status: 200 OK
{
"id": "ord_d0b6fmv28q6vn14peun0",
"number": 1001,
"customer_id": "cus_d0b6fmv28q6vn14peun0",
"location_id": null,
"status": "paid",
"refund_status": "none",
"shipping": 599,
"total": 3323,
"discount": 0,
"discount_reason": null,
"billing_zip": "12345",
"phone": "+13105551234",
"shipping_needed": true,
"gift_option": {
"name": "Gift wrap",
"description": "Kraft paper with a cotton ribbon.",
"price": 500
},
"gift_note": "Happy birthday, Sam!",
"shipping_rate": {"name": "Ground", "transit": {"min": 3, "max": 5}},
"tax": 225,
"prices_include_tax": false,
"taxes": [
{
"country": "US",
"region": "CA",
"name": "Sales tax",
"rate": "7.25",
"taxable": 3098,
"tax": 225,
"tax_id": null
}
],
"gift_card": {
"last4": "QX4P",
"amount": 1000
},
"amount_charged": 2323,
"items": [
{
"order_variant_id": "orv_d0b6fmv28q6vn14peun0",
"product_id": "pro_d0b6fmv28q6vn14peun0",
"product_name": "Premium T-Shirt",
"variant_name": "Small Red",
"quantity": 1,
"price": 1999
}
],
"downloads": [
{
"id": "ovd_d0b6fmv28q6vn14peun0",
"product_name": "Ebook",
"variant_name": "PDF",
"download_url": "https://shop.easel.engineering/downloads/...",
"download_count": 1,
"downloads_remaining": 4,
"status": "active",
"expires_at": "2023-02-01T10:00:00Z",
"created_at": "2023-01-01T10:00:00Z"
}
],
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T10:15:00Z",
"canceled_at": null,
"canceled_by": null
}
An order placed for no one, such as a sale in the shop to someone who gave
no name or email, has customer_id set to null. A guest who checks out
with an email is a customer, so their orders have one.
Error Responses
Order not found
Status: 404 Not Found
{
"error": "Resource not found",
"code": "NOT_FOUND"
}
Failed to get order
Status: 500 Internal Server Error
{
"error": "Internal server error",
"code": "INTERNAL_ERROR"
}
Update Order
Update an existing order's status.
Endpoint: PUT /api/v1/orders/{order_id}/
Scope Required: orders:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
order_id |
string | Order public ID (e.g., ord_d0b6fmv28q6vn14peun0) |
Request Body
{
"status": "fulfilled"
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | Order status: pending, paid, or fulfilled |
Discounts and billing details are not editable through this endpoint.
Request
curl -X PUT "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/" \
-H "Authorization: Bearer your_api_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-12345" \
-d '{
"status": "fulfilled"
}'
Optional Headers:
Idempotency-Key: Unique string to prevent duplicate order updates (see Idempotency)
Response
Returns the updated order.
Status: 200 OK
{
"id": "ord_d0b6fmv28q6vn14peun0",
"number": 1001,
"customer_id": "cus_d0b6fmv28q6vn14peun0",
"location_id": null,
"status": "fulfilled",
"refund_status": "none",
"shipping": 599,
"total": 2598,
"discount": 0,
"discount_reason": null,
"billing_zip": "12345",
"phone": "+13105551234",
"shipping_needed": true,
"gift_option": null,
"gift_note": null,
"shipping_rate": {"name": "Ground", "transit": {"min": 3, "max": 5}},
"tax": 0,
"prices_include_tax": false,
"taxes": [],
"gift_card": null,
"amount_charged": 2598,
"items": [
{
"order_variant_id": "orv_d0b6fmv28q6vn14peun0",
"product_id": "pro_d0b6fmv28q6vn14peun0",
"product_name": "Premium T-Shirt",
"variant_name": "Small Red",
"quantity": 1,
"price": 1999
}
],
"downloads": [],
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T11:30:00Z",
"canceled_at": null,
"canceled_by": null
}
Error Responses
Invalid JSON body
Status: 400 Bad Request
{
"error": "Invalid JSON body",
"code": "INVALID_JSON"
}
Missing required field
Status: 400 Bad Request
{
"error": "Order status is required",
"code": "MISSING_FIELD"
}
Invalid status
Status: 400 Bad Request
{
"error": "Status must be 'pending', 'paid', or 'fulfilled'",
"code": "INVALID_FIELD"
}
Order not found
Status: 404 Not Found
{
"error": "Resource not found",
"code": "NOT_FOUND"
}
Failed to update order
Status: 500 Internal Server Error
{
"error": "Internal server error",
"code": "INTERNAL_ERROR"
}
List Order Events
Retrieve an order's history, newest first: what happened to it, when, and
who did it. Events are read-only. Easel records each one in the same
transaction as the change it describes, such as a refund or a shipment, and
never edits or deletes them; a correction is a later event.
Endpoint: GET /api/v1/orders/{order_id}/events/
Scope Required: orders:read
Query Parameters
| Parameter | Type | Description |
|---|---|---|
after |
string | Cursor for pagination. Event ID to return older events after |
before |
string | Cursor for pagination. Event ID to return the newer events before, for paging back |
limit |
integer | Number of events to return (default 10, max 100) |
Events come newest first either way. The response reports has_more, which
is true when more events follow in the direction of travel: older ones, or
newer ones with before. A malformed after or before, both at once, or an
invalid limit returns INVALID_REQUEST_ERROR.
Request
curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/events/?limit=3" \
-H "Authorization: Bearer your_api_key_here"
Response
Status: 200 OK
{
"data": [
{
"id": "oev_d0b6fmv28q6vn14pev05",
"type": "status_changed",
"actor": { "type": "user", "name": "Ada" },
"details": { "from": "paid", "to": "fulfilled" },
"created_at": "2023-01-02T09:00:00Z"
},
{
"id": "oev_d0b6fmv28q6vn14pev04",
"type": "shipment_created",
"actor": { "type": "user", "name": "Ada" },
"details": {
"shipment_id": "shp_d0b6fr728q6vn14peuu0",
"shipment_number": 1,
"items": [
{ "order_variant_id": "orv_d0b6fmv28q6vn14peuog", "quantity": 1 }
]
},
"created_at": "2023-01-02T09:00:00Z"
},
{
"id": "oev_d0b6fmv28q6vn14pev02",
"type": "paid",
"actor": { "type": "customer", "name": null },
"details": { "amount": 2598 },
"created_at": "2023-01-01T10:00:00Z"
}
],
"has_more": true
}
Error Responses
Order not found
Status: 404 Not Found
{
"error": "Resource not found",
"code": "NOT_FOUND"
}
Order Event Object
| Field | Type | Description |
|---|---|---|
id |
string | Event public ID |
type |
string | What happened (see below) |
actor |
object | Who did it (see below) |
details |
object | Fields that depend on type; fields that don't apply are left out |
created_at |
string | ISO 8601 timestamp |
Actor Object
| Field | Type | Description |
|---|---|---|
type |
string | user (a dashboard user), api_key, customer, stripe (a webhook, or a change made in the Stripe dashboard), platform (an Easel staff member using the access an owner gave them), or system (Easel itself) |
name |
string|null | The user's, API key's, or Easel staff member's name; null for other actors, or once that user or key is deleted |
Event Types
| Type | Details |
|---|---|
created |
total |
paid |
amount |
status_changed |
from and to, each an order status |
refunded |
amount, refund_id, and reason when one was given |
restocked |
items put back in stock |
canceled |
reason when one was given |
shipment_created |
shipment_id, shipment_number, items, and tracking when it has a tracking number |
shipment_updated |
shipment_id, shipment_number, items, tracking; a changed tracking number also sets from and to |
shipment_reverted |
shipment_id, shipment_number, and the cleared tracking number in from |
shipment_canceled |
shipment_id, shipment_number, and reason when one was given |
address_edited |
from and to, each the address on one line |
gift_options_changed |
from and to |
note_added |
note |
email_sent |
email: order_confirmation or shipment (with shipment_id and shipment_number) |
dispute_opened |
amount and reason, as Stripe reports them, and dispute_id, Stripe's ID for the dispute |
dispute_closed |
amount, dispute_id, and reason: Stripe's outcome, such as won, lost, or warning_closed |
gift_card_redeemed |
amount the gift card paid, and gift_card_id and gift_card_last4 naming the card |
gift_card_issued |
amount the card is worth, to, the email it was sent to, and gift_card_id and gift_card_last4 |
gift_card_refunded |
amount of a refund put back on the card that paid, refund_id, and gift_card_id and gift_card_last4 |
download_revoked |
download_id, the order_variant_id it's for, and file_name when the item has a file |
download_regenerated |
download_id, order_variant_id, and file_name as for download_revoked; the old link no longer works |
Amounts are in the store's smallest currency unit. Each item in items has
an order_variant_id and a quantity. New event types may be added, so
ignore types you don't recognize.
Order Object
Fields
| Field | Type | Description |
|---|---|---|
id |
string | Order public ID |
number |
integer | Human-readable order number |
customer_id |
string|null | Customer public ID, or null for an order placed for no one |
location_id |
string|null | Public ID of the location it was sold at in person, or null for an order bought online or shipped |
status |
string | Order status: pending, paid, fulfilled |
refund_status |
string | none, partially_refunded, or refunded |
shipping |
integer | Shipping cost in the store's smallest currency unit |
total |
integer | Total order amount in the store's smallest currency unit |
discount |
integer | Discount amount in the store's smallest currency unit |
discount_reason |
string|null | Reason for the discount |
billing_zip |
string|null | Billing ZIP code |
phone |
string|null | The phone number the buyer gave with the shipping address for the carrier to call about delivery, in E.164 form such as "+13105551234", or null when they gave none or the order doesn't ship. It is personal data: show it only to the people who fulfill the order |
shipping_needed |
boolean | Whether the order contains physical items |
gift_option |
object|null | The gift option the buyer chose, such as gift wrapping, or null. See Gift Option Object |
gift_note |
string|null | The note the buyer asked to send with the gift, or null |
shipping_rate |
object|null | The shipping rate the buyer chose, whose price is shipping, or null for an order that doesn't ship. See Order Shipping Rate Object |
tax |
integer | Tax the order collected, already part of total |
prices_include_tax |
boolean | true when the prices already included the tax, as in the EU; false when checkout added it on top |
taxes |
array | Tax by jurisdiction (single order only). See Order Tax Object |
gift_card |
object|null | The gift card that paid part of total: last4 and amount, or null |
amount_charged |
integer | What the buyer's card paid: total less the gift card's amount |
items |
array | Order line items (single order only) |
downloads |
array | Download records for digital items (single order only) |
created_at |
string | ISO 8601 timestamp |
updated_at |
string | ISO 8601 timestamp |
canceled_at |
string|null | ISO 8601 timestamp of when the order was canceled, or null |
canceled_by |
string|null | Who canceled the order: customer, or null |
Order Shipping Rate Object
The shipping rate as the buyer chose it. It is copied onto the order at
purchase, so renaming, repricing, or deleting the site's rate later never
changes it. Its price at purchase is the order's shipping.
| Field | Type | Description |
|---|---|---|
name |
string | Rate name, such as Ground |
transit |
object | Business days the carrier was to take, as min and max |
Gift Option Object
The gift option as the buyer bought it. It is copied onto the order at
purchase, so editing or deleting the site's gift option
later never changes it.
| Field | Type | Description |
|---|---|---|
name |
string | Gift option name |
description |
string | Gift option description, possibly empty |
price |
integer | What the buyer paid for it, in the store's smallest currency unit |
The gift option's price is part of total, charged on its own apart from the
items and shipping. A discount code never reduces it, and a refund can cover
it like any other part of the total.
Gift Cards
When a gift card paid part or all of an order, gift_card names it by the
last four characters of its code and says how much it paid, and
amount_charged is what was charged to the buyer's card. An order a gift
card paid in full has an amount_charged of 0. See
Gift Cards.
Order History Events
An order's history (GET /api/v1/orders/{order_id}/events/) records gift
cards with these event types. Their details carry gift_card_id and
gift_card_last4, never the code:
gift_card_redeemed- A gift card paidamountof the ordergift_card_issued- The order bought a gift card worthamount, emailed
to the address intogift_card_refunded- A refund putamountback on the gift card, with
refund_id
An order a gift card paid in full has no paid event, since nothing was
charged.
Order Status Values
pending- Order created but not yet paidpaid- Payment receivedfulfilled- Every item has shipped, or the order had nothing to ship
Canceled Orders
A buyer with a storefront account can cancel their own order for a short
time after paying, set per site by cancel_window_minutes (see
Sites), as long as no shipment exists and no download link
has been used. Canceling refunds the order in full through Stripe, puts
its shipped items back on stock, and revokes its download links.
A canceled order keeps its status, has refund_status refunded, and
sets canceled_at and canceled_by. It can't take a shipment: creating
one returns 409 Conflict with the code ORDER_CANCELED.
Order Item Object
The items array lists the line items on an order. The order_variant_id is
the value you pass to the Shipments endpoints when you
ship all or part of an order.
| Field | Type | Description |
|---|---|---|
order_variant_id |
string | Ordered line item public ID, used in shipments |
product_id |
string | Product public ID |
product_name |
string | Product name |
variant_name |
string | Variant name |
quantity |
integer | Quantity ordered |
price |
integer | Unit price in the store's smallest currency unit |
Order Tax Object
The taxes array breaks an order's tax down by the jurisdictions that
charged it, as they were when the order was placed. They never change
afterward, even if the store's registrations or rates do. A list of orders
returns taxes empty, like items; retrieve one order to see them.
| Field | Type | Description |
|---|---|---|
country |
string | ISO 3166-1 alpha-2 country code |
region |
string|null | Subdivision code, such as CA for California, or null for a whole country |
name |
string | Name of the tax, such as VAT, GST, or Sales tax |
rate |
string | Rate as a percentage, such as "7.25" |
taxable |
integer | Amount taxed, before tax, in the store's smallest currency unit |
tax |
integer | Tax charged in the store's smallest currency unit |
tax_id |
string|null | The store's tax ID there, such as a VAT number |
Download Object
See Order Downloads for the full download object.