Store Hugs Developers

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(array $query = []): array

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 array The query fields below, by name.

Query fields

ParameterTypeDescription
statusany
customerIdinteger
quotationNumberstringAn exact quotation number. At most 50 characters.
limitintegerAt least 1. At most 100. Default 50.
cursorstringmeta.nextCursor (or the cursor in links.next) of the previous page. At most 1000 characters.
updatedSincestring (date-time)Records changed at or after this time (ISO 8601, any offset or Z), oldest change first, for incremental sync.

Returns

An array with data (a list of AdminQuotation), links and meta:

FieldTypeDescription
linksobject
links.firstnull
links.lastnull
links.prevstring (uri) or null
links.nextstring (uri) or null
metaobject
meta.pathstring (uri)
meta.perPageinteger
meta.nextCursorstring or null
meta.prevCursorstring or null
dataarray of AdminQuotation
data[].idinteger
data[].quotationNumberstring
data[].statusstringOne of draft, published, agreed, superseded, cancelled.
data[].enquiryIdinteger or null
data[].orderIdinteger or null
data[].previousQuotationIdinteger or null
data[].customerIdinteger or null
data[].customerNamestring or null
data[].customerEmailstring or null
data[].customerPhonestring or null
data[].shippingCountryCodestring or null
data[].shippingCitystring or null
data[].shippingStatestring or null
data[].shippingAddressLine1string or null
data[].shippingAddressLine2string or null
data[].shippingPostalCodestring or null
data[].billingDifferentboolean
data[].billingNamestring or null
data[].billingPhonestring or null
data[].billingCountryCodestring or null
data[].billingCitystring or null
data[].billingStatestring or null
data[].billingAddressLine1string or null
data[].billingAddressLine2string or null
data[].billingPostalCodestring or null
data[].notesstring or null
data[].currencystring or null
data[].itemsarray of object
data[].items[].productIdinteger or null
data[].items[].titlestring or null
data[].items[].skustring or null
data[].items[].quantityinteger
data[].items[].pricenumber
data[].items[].totalnumber
data[].items[].notestring or null
data[].subtotalnumber
data[].shippingFeenumber
data[].taxnumber
data[].totalnumber
data[].allowedActionsarray of stringThe moves the quotation allows now.
data[].sentAtstring (date-time) or null
data[].agreedAtstring (date-time) or null
data[].cancelledAtstring (date-time) or null
data[].createdAtstring (date-time) or null
data[].updatedAtstring (date-time) or null

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
422Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid).
429Rate limited.

Example

$page = $admin->quotations->list(['customerId' => 88]);

Iterate over every quotation

$admin->quotations->all(array $query = []): Generator

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

A generator of AdminQuotation arrays, each:

FieldTypeDescription
[].idinteger
[].quotationNumberstring
[].statusstringOne of draft, published, agreed, superseded, cancelled.
[].enquiryIdinteger or null
[].orderIdinteger or null
[].previousQuotationIdinteger or null
[].customerIdinteger or null
[].customerNamestring or null
[].customerEmailstring or null
[].customerPhonestring or null
[].shippingCountryCodestring or null
[].shippingCitystring or null
[].shippingStatestring or null
[].shippingAddressLine1string or null
[].shippingAddressLine2string or null
[].shippingPostalCodestring or null
[].billingDifferentboolean
[].billingNamestring or null
[].billingPhonestring or null
[].billingCountryCodestring or null
[].billingCitystring or null
[].billingStatestring or null
[].billingAddressLine1string or null
[].billingAddressLine2string or null
[].billingPostalCodestring or null
[].notesstring or null
[].currencystring or null
[].itemsarray of object
[].items[].productIdinteger or null
[].items[].titlestring or null
[].items[].skustring or null
[].items[].quantityinteger
[].items[].pricenumber
[].items[].totalnumber
[].items[].notestring or null
[].subtotalnumber
[].shippingFeenumber
[].taxnumber
[].totalnumber
[].allowedActionsarray of stringThe moves the quotation allows now.
[].sentAtstring (date-time) or null
[].agreedAtstring (date-time) or null
[].cancelledAtstring (date-time) or null
[].createdAtstring (date-time) or null
[].updatedAtstring (date-time) or null

Example

foreach ($admin->quotations->all(['updatedSince' => $lastSyncedAt]) as $quotation) {
    $erp->saveQuotation($quotation);
}

Get a quotation

$admin->quotations->get(int $id): array

One quotation by its id, with allowedActions: the moves its status allows now. Scope: quotations:read.

Parameters

Parameter Type
$id int The quotation's id.

Returns

An AdminQuotation:

FieldTypeDescription
idinteger
quotationNumberstring
statusstringOne of draft, published, agreed, superseded, cancelled.
enquiryIdinteger or null
orderIdinteger or null
previousQuotationIdinteger or null
customerIdinteger or null
customerNamestring or null
customerEmailstring or null
customerPhonestring or null
shippingCountryCodestring or null
shippingCitystring or null
shippingStatestring or null
shippingAddressLine1string or null
shippingAddressLine2string or null
shippingPostalCodestring or null
billingDifferentboolean
billingNamestring or null
billingPhonestring or null
billingCountryCodestring or null
billingCitystring or null
billingStatestring or null
billingAddressLine1string or null
billingAddressLine2string or null
billingPostalCodestring or null
notesstring or null
currencystring or null
itemsarray of object
items[].productIdinteger or null
items[].titlestring or null
items[].skustring or null
items[].quantityinteger
items[].pricenumber
items[].totalnumber
items[].notestring or null
subtotalnumber
shippingFeenumber
taxnumber
totalnumber
allowedActionsarray of stringThe moves the quotation allows now.
sentAtstring (date-time) or null
agreedAtstring (date-time) or null
cancelledAtstring (date-time) or null
createdAtstring (date-time) or null
updatedAtstring (date-time) or null

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
404not_found: the shop has no such record.
429Rate limited.

Draft a quotation

$admin->quotations->create(array $input, string|false|null $idempotencyKey = null): array

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 array The quotation's fields, below.
$idempotencyKey string|false|null Your own Idempotency-Key, or false for none. One is made for you when null.

Request fields

FieldTypeDescription
customerIdinteger or null
customerNamestring or nullRequired without customerId. At most 255 characters.
customerEmailstring (email) or nullAt most 255 characters.
customerPhonestring or nullAt most 50 characters.
items requiredarray of objectReplaces every line when updating. At most 200 items.
items[].productId requiredintegerOne of the shop's products, each once.
items[].quantity requiredintegerAt least 1. At most 1000000.
items[].price requirednumberAt least 0. At most 9999999999.
items[].notestring or nullAt most 500 characters.
shippingFee requirednumberAt least 0. At most 9999999999.
shippingCountryCodestring or nullAt most 3 characters.
shippingCitystring or nullAt most 100 characters.
shippingStatestring or nullAt most 100 characters.
shippingAddressLine1string or nullAt most 255 characters.
shippingAddressLine2string or nullAt most 255 characters.
shippingPostalCodestring or nullAt most 20 characters.
billingDifferentboolean or nullUnset or false bills the shipping address and ignores the billing fields.
billingNamestring or nullAt most 255 characters.
billingPhonestring or nullAt most 50 characters.
billingCountryCodestring or nullAt most 3 characters.
billingCitystring or nullAt most 100 characters.
billingStatestring or nullAt most 100 characters.
billingAddressLine1string or nullAt most 255 characters.
billingAddressLine2string or nullAt most 255 characters.
billingPostalCodestring or nullAt most 20 characters.
notesstring or nullAt most 2000 characters.
introstring or nullRich text before the products, sanitized on save. Not returned in responses. At most 20000 characters.
pdfTemplateIdinteger or nullThe PDF layout; empty uses the shop's default. Not returned in responses.

Returns

The new draft AdminQuotation:

FieldTypeDescription
idinteger
quotationNumberstring
statusstringOne of draft, published, agreed, superseded, cancelled.
enquiryIdinteger or null
orderIdinteger or null
previousQuotationIdinteger or null
customerIdinteger or null
customerNamestring or null
customerEmailstring or null
customerPhonestring or null
shippingCountryCodestring or null
shippingCitystring or null
shippingStatestring or null
shippingAddressLine1string or null
shippingAddressLine2string or null
shippingPostalCodestring or null
billingDifferentboolean
billingNamestring or null
billingPhonestring or null
billingCountryCodestring or null
billingCitystring or null
billingStatestring or null
billingAddressLine1string or null
billingAddressLine2string or null
billingPostalCodestring or null
notesstring or null
currencystring or null
itemsarray of object
items[].productIdinteger or null
items[].titlestring or null
items[].skustring or null
items[].quantityinteger
items[].pricenumber
items[].totalnumber
items[].notestring or null
subtotalnumber
shippingFeenumber
taxnumber
totalnumber
allowedActionsarray of stringThe moves the quotation allows now.
sentAtstring (date-time) or null
agreedAtstring (date-time) or null
cancelledAtstring (date-time) or null
createdAtstring (date-time) or null
updatedAtstring (date-time) or null

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
409idempotency_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.
422Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid).
429Rate limited.

Example

$quotation = $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(
    int $id,
    array $changes,
    DateTimeInterface|string|null $unmodifiedSince = null,
    string|false|null $idempotencyKey = null,
): array

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 int The quotation's id.
$changes array The fields to change, below.
$unmodifiedSince DateTimeInterface|string|null The quotation's updatedAt as you read it. If it changed since, the change is refused with 409 edit_conflict.
$idempotencyKey string|false|null Your own Idempotency-Key, or false for none.

Request fields

FieldTypeDescription
customerIdinteger or null
customerNamestring or nullRequired without customerId. At most 255 characters.
customerEmailstring (email) or nullAt most 255 characters.
customerPhonestring or nullAt most 50 characters.
itemsarray of objectReplaces every line when updating. At most 200 items.
items[].productId requiredintegerOne of the shop's products, each once.
items[].quantity requiredintegerAt least 1. At most 1000000.
items[].price requirednumberAt least 0. At most 9999999999.
items[].notestring or nullAt most 500 characters.
shippingFeenumberAt least 0. At most 9999999999.
shippingCountryCodestring or nullAt most 3 characters.
shippingCitystring or nullAt most 100 characters.
shippingStatestring or nullAt most 100 characters.
shippingAddressLine1string or nullAt most 255 characters.
shippingAddressLine2string or nullAt most 255 characters.
shippingPostalCodestring or nullAt most 20 characters.
billingDifferentboolean or nullUnset or false bills the shipping address and ignores the billing fields.
billingNamestring or nullAt most 255 characters.
billingPhonestring or nullAt most 50 characters.
billingCountryCodestring or nullAt most 3 characters.
billingCitystring or nullAt most 100 characters.
billingStatestring or nullAt most 100 characters.
billingAddressLine1string or nullAt most 255 characters.
billingAddressLine2string or nullAt most 255 characters.
billingPostalCodestring or nullAt most 20 characters.
notesstring or nullAt most 2000 characters.
introstring or nullRich text before the products, sanitized on save. Not returned in responses. At most 20000 characters.
pdfTemplateIdinteger or nullThe PDF layout; empty uses the shop's default. Not returned in responses.
updatedAtstring (date-time) or nullThe record's updatedAt as you read it; a newer record gets 409 edit_conflict.

Returns

The changed AdminQuotation:

FieldTypeDescription
idinteger
quotationNumberstring
statusstringOne of draft, published, agreed, superseded, cancelled.
enquiryIdinteger or null
orderIdinteger or null
previousQuotationIdinteger or null
customerIdinteger or null
customerNamestring or null
customerEmailstring or null
customerPhonestring or null
shippingCountryCodestring or null
shippingCitystring or null
shippingStatestring or null
shippingAddressLine1string or null
shippingAddressLine2string or null
shippingPostalCodestring or null
billingDifferentboolean
billingNamestring or null
billingPhonestring or null
billingCountryCodestring or null
billingCitystring or null
billingStatestring or null
billingAddressLine1string or null
billingAddressLine2string or null
billingPostalCodestring or null
notesstring or null
currencystring or null
itemsarray of object
items[].productIdinteger or null
items[].titlestring or null
items[].skustring or null
items[].quantityinteger
items[].pricenumber
items[].totalnumber
items[].notestring or null
subtotalnumber
shippingFeenumber
taxnumber
totalnumber
allowedActionsarray of stringThe moves the quotation allows now.
sentAtstring (date-time) or null
agreedAtstring (date-time) or null
cancelledAtstring (date-time) or null
createdAtstring (date-time) or null
updatedAtstring (date-time) or null

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
404not_found: the shop has no such record.
409edit_conflict (the record changed after If-Unmodified-Since or updatedAt), or idempotency_in_progress.
422Validation errors, invalid_transition, or an idempotency refusal.
429Rate limited.

Example

$quotation = $admin->quotations->get(7);
$admin->quotations->update(7, ['shippingFee' => 25], unmodifiedSince: $quotation['updatedAt']);

Email a quotation

$admin->quotations->send(int $id, array $input = [], string|false|null $idempotencyKey = null): array

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 int The quotation's id.
$input array The email's fields, below; all optional.
$idempotencyKey string|false|null Your own Idempotency-Key, or false for none. One is made for you when null.

Request fields

FieldTypeDescription
recipientstring (email) or nullAt most 255 characters.
ccarray of string (email) or nullAt most 5 items.
subjectstring or nullAt most 255 characters.
messagestring or nullAt most 5000 characters.

Returns

The sent AdminQuotation:

FieldTypeDescription
idinteger
quotationNumberstring
statusstringOne of draft, published, agreed, superseded, cancelled.
enquiryIdinteger or null
orderIdinteger or null
previousQuotationIdinteger or null
customerIdinteger or null
customerNamestring or null
customerEmailstring or null
customerPhonestring or null
shippingCountryCodestring or null
shippingCitystring or null
shippingStatestring or null
shippingAddressLine1string or null
shippingAddressLine2string or null
shippingPostalCodestring or null
billingDifferentboolean
billingNamestring or null
billingPhonestring or null
billingCountryCodestring or null
billingCitystring or null
billingStatestring or null
billingAddressLine1string or null
billingAddressLine2string or null
billingPostalCodestring or null
notesstring or null
currencystring or null
itemsarray of object
items[].productIdinteger or null
items[].titlestring or null
items[].skustring or null
items[].quantityinteger
items[].pricenumber
items[].totalnumber
items[].notestring or null
subtotalnumber
shippingFeenumber
taxnumber
totalnumber
allowedActionsarray of stringThe moves the quotation allows now.
sentAtstring (date-time) or null
agreedAtstring (date-time) or null
cancelledAtstring (date-time) or null
createdAtstring (date-time) or null
updatedAtstring (date-time) or null

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
404not_found: the shop has no such record.
409idempotency_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.
422Validation errors (a missing recipient included), invalid_transition, or an idempotency refusal.
429Rate limited.

Example

$admin->quotations->send($quotation['id'], ['message' => 'Here is our offer.']);

Move a quotation along

$admin->quotations->perform(
    int $id,
    QuotationAction $action,
    ?string $note = null,
    string|false|null $idempotencyKey = null,
): array

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 StoreHugs\Integration\Admin\QuotationAction:

Case Value
QuotationAction::Publish publish Publishes the quotation, without an email.
QuotationAction::Revise revise Makes a new draft, returned in data (answered 201).
QuotationAction::Agree agree Marks the quotation agreed.
QuotationAction::AgreeAndOrder agree_and_order Agrees and makes an order, returned in order.
QuotationAction::CreateOrder create_order Makes an order, returned in order.
QuotationAction::Cancel cancel Cancels the quotation.

Parameters

Parameter Type
$id int The quotation's id.
$action QuotationAction The move to make, above.
$note ?string An optional note, at most 1,000 characters. Not sent when null.
$idempotencyKey string|false|null Your own Idempotency-Key, or false for none. One is made for you when null.

Returns

A QuotationActionResult array: data is the quotation (for Revise, the new draft), and order the order made by AgreeAndOrder or CreateOrder, otherwise null:

FieldTypeDescription
dataAdminQuotation
data.idinteger
data.quotationNumberstring
data.statusstringOne of draft, published, agreed, superseded, cancelled.
data.enquiryIdinteger or null
data.orderIdinteger or null
data.previousQuotationIdinteger or null
data.customerIdinteger or null
data.customerNamestring or null
data.customerEmailstring or null
data.customerPhonestring or null
data.shippingCountryCodestring or null
data.shippingCitystring or null
data.shippingStatestring or null
data.shippingAddressLine1string or null
data.shippingAddressLine2string or null
data.shippingPostalCodestring or null
data.billingDifferentboolean
data.billingNamestring or null
data.billingPhonestring or null
data.billingCountryCodestring or null
data.billingCitystring or null
data.billingStatestring or null
data.billingAddressLine1string or null
data.billingAddressLine2string or null
data.billingPostalCodestring or null
data.notesstring or null
data.currencystring or null
data.itemsarray of object
data.items[].productIdinteger or null
data.items[].titlestring or null
data.items[].skustring or null
data.items[].quantityinteger
data.items[].pricenumber
data.items[].totalnumber
data.items[].notestring or null
data.subtotalnumber
data.shippingFeenumber
data.taxnumber
data.totalnumber
data.allowedActionsarray of stringThe moves the quotation allows now.
data.sentAtstring (date-time) or null
data.agreedAtstring (date-time) or null
data.cancelledAtstring (date-time) or null
data.createdAtstring (date-time) or null
data.updatedAtstring (date-time) or null
orderobject or null
order.idinteger
order.orderNumberstring

Errors

StatusWhen
401invalid_api_key: no active Admin key in Authorization: Bearer.
403admin_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.
404not_found: the shop has no such record.
409idempotency_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.
422Validation errors, invalid_transition, or an idempotency refusal.
429Rate limited.

Example

use StoreHugs\Integration\Admin\QuotationAction;

$order = $admin->quotations->perform($quotation['id'], QuotationAction::CreateOrder)['order'];