Quotations
B2B quotations, from draft to order: draft one, email it as a PDF, then move it along until it becomes an order.
Reach them through admin.quotations. Reading needs the quotations:read scope, changing them quotations:write. Quotations need a plan with quotations (403 plan_required).
List quotations
admin.quotations.list(query?: ListQuery & { status?: string; customerId?: number; quotationNumber?: string }): Promise<CursorPage<AdminQuotation>>
One page of the shop's quotations, newest first, or oldest change first with updatedSince. Read the next page with meta.nextCursor as cursor. Scope: quotations:read.
Parameters
| Parameter |
Type |
|
query |
object |
The query fields below, by name. |
Query fields
| Parameter | Type | Description |
|---|
status | any | |
customerId | integer | |
quotationNumber | string | An exact quotation number. At most 50 characters. |
limit | integer | At least 1. At most 100. Default 50. |
cursor | string | meta.nextCursor (or the cursor in links.next) of the previous page. At most 1000 characters. |
updatedSince | string (date-time) | Records changed at or after this time (ISO 8601, any offset or Z), oldest change first, for incremental sync. |
Returns
A CursorPage<AdminQuotation>:
| Field | Type | Description |
|---|
links | object | |
links.first | null | |
links.last | null | |
links.prev | string (uri) or null | |
links.next | string (uri) or null | |
meta | object | |
meta.path | string (uri) | |
meta.perPage | integer | |
meta.nextCursor | string or null | |
meta.prevCursor | string or null | |
data | array of AdminQuotation | |
data[].id | integer | |
data[].quotationNumber | string | |
data[].status | string | One of draft, published, agreed, superseded, cancelled. |
data[].enquiryId | integer or null | |
data[].orderId | integer or null | |
data[].previousQuotationId | integer or null | |
data[].customerId | integer or null | |
data[].customerName | string or null | |
data[].customerEmail | string or null | |
data[].customerPhone | string or null | |
data[].shippingCountryCode | string or null | |
data[].shippingCity | string or null | |
data[].shippingState | string or null | |
data[].shippingAddressLine1 | string or null | |
data[].shippingAddressLine2 | string or null | |
data[].shippingPostalCode | string or null | |
data[].billingDifferent | boolean | |
data[].billingName | string or null | |
data[].billingPhone | string or null | |
data[].billingCountryCode | string or null | |
data[].billingCity | string or null | |
data[].billingState | string or null | |
data[].billingAddressLine1 | string or null | |
data[].billingAddressLine2 | string or null | |
data[].billingPostalCode | string or null | |
data[].notes | string or null | |
data[].currency | string or null | |
data[].items | array of object | |
data[].items[].productId | integer or null | |
data[].items[].title | string or null | |
data[].items[].sku | string or null | |
data[].items[].quantity | integer | |
data[].items[].price | number | |
data[].items[].total | number | |
data[].items[].note | string or null | |
data[].subtotal | number | |
data[].shippingFee | number | |
data[].tax | number | |
data[].total | number | |
data[].allowedActions | array of string | The moves the quotation allows now. |
data[].sentAt | string (date-time) or null | |
data[].agreedAt | string (date-time) or null | |
data[].cancelledAt | string (date-time) or null | |
data[].createdAt | string (date-time) or null | |
data[].updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
422 | Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid). |
429 | Rate limited. |
Example
const { data } = await admin.quotations.list({ customerId: 88 });
Iterate over every quotation
admin.quotations.all(query?: ListQuery & { status?: string; customerId?: number; quotationNumber?: string }): AsyncGenerator<AdminQuotation, void, undefined>
Every quotation matching the query, one at a time, fetching the next page as you read. It takes the same query as list, without cursor. Scope: quotations:read.
Returns
An async iterable of AdminQuotation, each:
| Field | Type | Description |
|---|
[].id | integer | |
[].quotationNumber | string | |
[].status | string | One of draft, published, agreed, superseded, cancelled. |
[].enquiryId | integer or null | |
[].orderId | integer or null | |
[].previousQuotationId | integer or null | |
[].customerId | integer or null | |
[].customerName | string or null | |
[].customerEmail | string or null | |
[].customerPhone | string or null | |
[].shippingCountryCode | string or null | |
[].shippingCity | string or null | |
[].shippingState | string or null | |
[].shippingAddressLine1 | string or null | |
[].shippingAddressLine2 | string or null | |
[].shippingPostalCode | string or null | |
[].billingDifferent | boolean | |
[].billingName | string or null | |
[].billingPhone | string or null | |
[].billingCountryCode | string or null | |
[].billingCity | string or null | |
[].billingState | string or null | |
[].billingAddressLine1 | string or null | |
[].billingAddressLine2 | string or null | |
[].billingPostalCode | string or null | |
[].notes | string or null | |
[].currency | string or null | |
[].items | array of object | |
[].items[].productId | integer or null | |
[].items[].title | string or null | |
[].items[].sku | string or null | |
[].items[].quantity | integer | |
[].items[].price | number | |
[].items[].total | number | |
[].items[].note | string or null | |
[].subtotal | number | |
[].shippingFee | number | |
[].tax | number | |
[].total | number | |
[].allowedActions | array of string | The moves the quotation allows now. |
[].sentAt | string (date-time) or null | |
[].agreedAt | string (date-time) or null | |
[].cancelledAt | string (date-time) or null | |
[].createdAt | string (date-time) or null | |
[].updatedAt | string (date-time) or null | |
Example
for await (const quotation of admin.quotations.all({
updatedSince: lastSyncedAt,
})) {
await erp.saveQuotation(quotation);
}
Get a quotation
admin.quotations.get(id: number): Promise<AdminQuotation>
One quotation by its id, with allowedActions: the moves its status allows now. Scope: quotations:read.
Parameters
| Parameter |
Type |
|
id |
number |
The quotation's id. |
Returns
An AdminQuotation:
| Field | Type | Description |
|---|
id | integer | |
quotationNumber | string | |
status | string | One of draft, published, agreed, superseded, cancelled. |
enquiryId | integer or null | |
orderId | integer or null | |
previousQuotationId | integer or null | |
customerId | integer or null | |
customerName | string or null | |
customerEmail | string or null | |
customerPhone | string or null | |
shippingCountryCode | string or null | |
shippingCity | string or null | |
shippingState | string or null | |
shippingAddressLine1 | string or null | |
shippingAddressLine2 | string or null | |
shippingPostalCode | string or null | |
billingDifferent | boolean | |
billingName | string or null | |
billingPhone | string or null | |
billingCountryCode | string or null | |
billingCity | string or null | |
billingState | string or null | |
billingAddressLine1 | string or null | |
billingAddressLine2 | string or null | |
billingPostalCode | string or null | |
notes | string or null | |
currency | string or null | |
items | array of object | |
items[].productId | integer or null | |
items[].title | string or null | |
items[].sku | string or null | |
items[].quantity | integer | |
items[].price | number | |
items[].total | number | |
items[].note | string or null | |
subtotal | number | |
shippingFee | number | |
tax | number | |
total | number | |
allowedActions | array of string | The moves the quotation allows now. |
sentAt | string (date-time) or null | |
agreedAt | string (date-time) or null | |
cancelledAt | string (date-time) or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
429 | Rate limited. |
Draft a quotation
admin.quotations.create(input: AdminQuotationInput, options?: RequestOptions): Promise<AdminQuotation>
A draft quotation for one of the shop's customers (customerId), or without one a named person (customerName). items and shippingFee are required. Nothing is emailed: send or publish it next. Scope: quotations:write.
Parameters
| Parameter |
Type |
|
input |
object |
The quotation's fields, below. |
options |
RequestOptions |
idempotencyKey, signal and headers (see retries). |
Request fields
| Field | Type | Description |
|---|
customerId | integer or null | |
customerName | string or null | Required without customerId. At most 255 characters. |
customerEmail | string (email) or null | At most 255 characters. |
customerPhone | string or null | At most 50 characters. |
items required | array of object | Replaces every line when updating. At most 200 items. |
items[].productId required | integer | One of the shop's products, each once. |
items[].quantity required | integer | At least 1. At most 1000000. |
items[].price required | number | At least 0. At most 9999999999. |
items[].note | string or null | At most 500 characters. |
shippingFee required | number | At least 0. At most 9999999999. |
shippingCountryCode | string or null | At most 3 characters. |
shippingCity | string or null | At most 100 characters. |
shippingState | string or null | At most 100 characters. |
shippingAddressLine1 | string or null | At most 255 characters. |
shippingAddressLine2 | string or null | At most 255 characters. |
shippingPostalCode | string or null | At most 20 characters. |
billingDifferent | boolean or null | Unset or false bills the shipping address and ignores the billing fields. |
billingName | string or null | At most 255 characters. |
billingPhone | string or null | At most 50 characters. |
billingCountryCode | string or null | At most 3 characters. |
billingCity | string or null | At most 100 characters. |
billingState | string or null | At most 100 characters. |
billingAddressLine1 | string or null | At most 255 characters. |
billingAddressLine2 | string or null | At most 255 characters. |
billingPostalCode | string or null | At most 20 characters. |
notes | string or null | At most 2000 characters. |
intro | string or null | Rich text before the products, sanitized on save. Not returned in responses. At most 20000 characters. |
pdfTemplateId | integer or null | The PDF layout; empty uses the shop's default. Not returned in responses. |
Returns
The new draft AdminQuotation:
| Field | Type | Description |
|---|
id | integer | |
quotationNumber | string | |
status | string | One of draft, published, agreed, superseded, cancelled. |
enquiryId | integer or null | |
orderId | integer or null | |
previousQuotationId | integer or null | |
customerId | integer or null | |
customerName | string or null | |
customerEmail | string or null | |
customerPhone | string or null | |
shippingCountryCode | string or null | |
shippingCity | string or null | |
shippingState | string or null | |
shippingAddressLine1 | string or null | |
shippingAddressLine2 | string or null | |
shippingPostalCode | string or null | |
billingDifferent | boolean | |
billingName | string or null | |
billingPhone | string or null | |
billingCountryCode | string or null | |
billingCity | string or null | |
billingState | string or null | |
billingAddressLine1 | string or null | |
billingAddressLine2 | string or null | |
billingPostalCode | string or null | |
notes | string or null | |
currency | string or null | |
items | array of object | |
items[].productId | integer or null | |
items[].title | string or null | |
items[].sku | string or null | |
items[].quantity | integer | |
items[].price | number | |
items[].total | number | |
items[].note | string or null | |
subtotal | number | |
shippingFee | number | |
tax | number | |
total | number | |
allowedActions | array of string | The moves the quotation allows now. |
sentAt | string (date-time) or null | |
agreedAt | string (date-time) or null | |
cancelledAt | string (date-time) or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
409 | idempotency_in_progress: a request with this Idempotency-Key is still running. Retry with the same key after Retry-After seconds; the first answer is then replayed. |
422 | Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid). |
429 | Rate limited. |
Example
const quotation = await admin.quotations.create({
customerId: 88,
items: [{ productId: 12, quantity: 500, price: 14 }],
shippingFee: 0,
intro: 'Prices valid for 30 days.',
});
Update a draft quotation
admin.quotations.update(id: number, changes: Partial<AdminQuotationInput>, options?: UpdateOptions): Promise<AdminQuotation>
Changes only the fields sent, and items replaces every line. A draft only (422 invalid_transition): revise a published quotation for a new draft. Scope: quotations:write.
Parameters
| Parameter |
Type |
|
id |
number |
The quotation's id. |
changes |
object |
The fields to change, below. |
options |
UpdateOptions |
unmodifiedSince (the updatedAt you read; a newer quotation is refused with 409 edit_conflict) and idempotencyKey. |
Request fields
| Field | Type | Description |
|---|
customerId | integer or null | |
customerName | string or null | Required without customerId. At most 255 characters. |
customerEmail | string (email) or null | At most 255 characters. |
customerPhone | string or null | At most 50 characters. |
items | array of object | Replaces every line when updating. At most 200 items. |
items[].productId required | integer | One of the shop's products, each once. |
items[].quantity required | integer | At least 1. At most 1000000. |
items[].price required | number | At least 0. At most 9999999999. |
items[].note | string or null | At most 500 characters. |
shippingFee | number | At least 0. At most 9999999999. |
shippingCountryCode | string or null | At most 3 characters. |
shippingCity | string or null | At most 100 characters. |
shippingState | string or null | At most 100 characters. |
shippingAddressLine1 | string or null | At most 255 characters. |
shippingAddressLine2 | string or null | At most 255 characters. |
shippingPostalCode | string or null | At most 20 characters. |
billingDifferent | boolean or null | Unset or false bills the shipping address and ignores the billing fields. |
billingName | string or null | At most 255 characters. |
billingPhone | string or null | At most 50 characters. |
billingCountryCode | string or null | At most 3 characters. |
billingCity | string or null | At most 100 characters. |
billingState | string or null | At most 100 characters. |
billingAddressLine1 | string or null | At most 255 characters. |
billingAddressLine2 | string or null | At most 255 characters. |
billingPostalCode | string or null | At most 20 characters. |
notes | string or null | At most 2000 characters. |
intro | string or null | Rich text before the products, sanitized on save. Not returned in responses. At most 20000 characters. |
pdfTemplateId | integer or null | The PDF layout; empty uses the shop's default. Not returned in responses. |
updatedAt | string (date-time) or null | The record's updatedAt as you read it; a newer record gets 409 edit_conflict. |
Returns
The changed AdminQuotation:
| Field | Type | Description |
|---|
id | integer | |
quotationNumber | string | |
status | string | One of draft, published, agreed, superseded, cancelled. |
enquiryId | integer or null | |
orderId | integer or null | |
previousQuotationId | integer or null | |
customerId | integer or null | |
customerName | string or null | |
customerEmail | string or null | |
customerPhone | string or null | |
shippingCountryCode | string or null | |
shippingCity | string or null | |
shippingState | string or null | |
shippingAddressLine1 | string or null | |
shippingAddressLine2 | string or null | |
shippingPostalCode | string or null | |
billingDifferent | boolean | |
billingName | string or null | |
billingPhone | string or null | |
billingCountryCode | string or null | |
billingCity | string or null | |
billingState | string or null | |
billingAddressLine1 | string or null | |
billingAddressLine2 | string or null | |
billingPostalCode | string or null | |
notes | string or null | |
currency | string or null | |
items | array of object | |
items[].productId | integer or null | |
items[].title | string or null | |
items[].sku | string or null | |
items[].quantity | integer | |
items[].price | number | |
items[].total | number | |
items[].note | string or null | |
subtotal | number | |
shippingFee | number | |
tax | number | |
total | number | |
allowedActions | array of string | The moves the quotation allows now. |
sentAt | string (date-time) or null | |
agreedAt | string (date-time) or null | |
cancelledAt | string (date-time) or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
409 | edit_conflict (the record changed after If-Unmodified-Since or updatedAt), or idempotency_in_progress. |
422 | Validation errors, invalid_transition, or an idempotency refusal. |
429 | Rate limited. |
Example
const quotation = await admin.quotations.get(7);
await admin.quotations.update(
7,
{ shippingFee: 25 },
{ unmodifiedSince: quotation.updatedAt ?? undefined },
);
Email a quotation
admin.quotations.send(id: number, input?: QuotationSendInput, options?: RequestOptions): Promise<AdminQuotation>
Emails the quotation as a PDF; a draft is published first. The recipient defaults to the quotation's customer email, the subject to the shop's default. A draft or published quotation only (422 invalid_transition). Scope: quotations:write.
Parameters
| Parameter |
Type |
|
id |
number |
The quotation's id. |
input |
object |
The email's fields, below; all optional. |
options |
RequestOptions |
idempotencyKey, signal and headers (see retries). |
Request fields
| Field | Type | Description |
|---|
recipient | string (email) or null | At most 255 characters. |
cc | array of string (email) or null | At most 5 items. |
subject | string or null | At most 255 characters. |
message | string or null | At most 5000 characters. |
Returns
The sent AdminQuotation:
| Field | Type | Description |
|---|
id | integer | |
quotationNumber | string | |
status | string | One of draft, published, agreed, superseded, cancelled. |
enquiryId | integer or null | |
orderId | integer or null | |
previousQuotationId | integer or null | |
customerId | integer or null | |
customerName | string or null | |
customerEmail | string or null | |
customerPhone | string or null | |
shippingCountryCode | string or null | |
shippingCity | string or null | |
shippingState | string or null | |
shippingAddressLine1 | string or null | |
shippingAddressLine2 | string or null | |
shippingPostalCode | string or null | |
billingDifferent | boolean | |
billingName | string or null | |
billingPhone | string or null | |
billingCountryCode | string or null | |
billingCity | string or null | |
billingState | string or null | |
billingAddressLine1 | string or null | |
billingAddressLine2 | string or null | |
billingPostalCode | string or null | |
notes | string or null | |
currency | string or null | |
items | array of object | |
items[].productId | integer or null | |
items[].title | string or null | |
items[].sku | string or null | |
items[].quantity | integer | |
items[].price | number | |
items[].total | number | |
items[].note | string or null | |
subtotal | number | |
shippingFee | number | |
tax | number | |
total | number | |
allowedActions | array of string | The moves the quotation allows now. |
sentAt | string (date-time) or null | |
agreedAt | string (date-time) or null | |
cancelledAt | string (date-time) or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
409 | idempotency_in_progress: a request with this Idempotency-Key is still running. Retry with the same key after Retry-After seconds; the first answer is then replayed. |
422 | Validation errors (a missing recipient included), invalid_transition, or an idempotency refusal. |
429 | Rate limited. |
Example
await admin.quotations.send(quotation.id, { message: 'Here is our offer.' });
admin.quotations.perform(id: number, action: QuotationAction, note?: string, options?: RequestOptions): Promise<QuotationActionResult>
Makes one move in the quotation's pipeline. The quotation's allowedActions lists the moves its status allows now; any other is 422 invalid_transition. Scope: quotations:write.
action is a QuotationAction:
| Value |
|
publish |
Publishes the quotation, without an email. |
revise |
Makes a new draft, returned in data (answered 201). |
agree |
Marks the quotation agreed. |
agree_and_order |
Agrees and makes an order, returned in order. |
create_order |
Makes an order, returned in order. |
cancel |
Cancels the quotation. |
Parameters
| Parameter |
Type |
|
id |
number |
The quotation's id. |
action |
QuotationAction |
The move to make, above. |
note |
string |
An optional note, at most 1,000 characters. |
options |
RequestOptions |
idempotencyKey, signal and headers (see retries). |
Returns
A QuotationActionResult: data is the quotation (for revise, the new draft), and order the order made by agree_and_order or create_order, otherwise null:
| Field | Type | Description |
|---|
data | AdminQuotation | |
data.id | integer | |
data.quotationNumber | string | |
data.status | string | One of draft, published, agreed, superseded, cancelled. |
data.enquiryId | integer or null | |
data.orderId | integer or null | |
data.previousQuotationId | integer or null | |
data.customerId | integer or null | |
data.customerName | string or null | |
data.customerEmail | string or null | |
data.customerPhone | string or null | |
data.shippingCountryCode | string or null | |
data.shippingCity | string or null | |
data.shippingState | string or null | |
data.shippingAddressLine1 | string or null | |
data.shippingAddressLine2 | string or null | |
data.shippingPostalCode | string or null | |
data.billingDifferent | boolean | |
data.billingName | string or null | |
data.billingPhone | string or null | |
data.billingCountryCode | string or null | |
data.billingCity | string or null | |
data.billingState | string or null | |
data.billingAddressLine1 | string or null | |
data.billingAddressLine2 | string or null | |
data.billingPostalCode | string or null | |
data.notes | string or null | |
data.currency | string or null | |
data.items | array of object | |
data.items[].productId | integer or null | |
data.items[].title | string or null | |
data.items[].sku | string or null | |
data.items[].quantity | integer | |
data.items[].price | number | |
data.items[].total | number | |
data.items[].note | string or null | |
data.subtotal | number | |
data.shippingFee | number | |
data.tax | number | |
data.total | number | |
data.allowedActions | array of string | The moves the quotation allows now. |
data.sentAt | string (date-time) or null | |
data.agreedAt | string (date-time) or null | |
data.cancelledAt | string (date-time) or null | |
data.createdAt | string (date-time) or null | |
data.updatedAt | string (date-time) or null | |
order | object or null | |
order.id | integer | |
order.orderNumber | string | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
409 | idempotency_in_progress: a request with this Idempotency-Key is still running. Retry with the same key after Retry-After seconds; the first answer is then replayed. |
422 | Validation errors, invalid_transition, or an idempotency refusal. |
429 | Rate limited. |
Example
const { order } = await admin.quotations.perform(quotation.id, 'create_order');