Orders
Account Scope Required, please include X-Auth-Account header with your
Account ID. Index/show require the billing:read OAuth scope;
create / update / destroy are not available via OAuth (there is
intentionally no billing:write scope) — they require a session or API-key
credential.
Ordering flows through the cart/billing system and charges the account's default saved payment method off-session. There is no Stripe checkout redirect.
Two order types
POST /api/orders provisions WordPress sites.
POST /api/orders/domain registers or
transfers a domain through the same cart/billing flow. Both always return
a 202 cart envelope and are OAuth-blocked (require a session or API-key
credential).
List all orders for an account
GET /api/orders
Params (optional)
- page: Integer | page number (default: 1)
- per_page: Integer | records per page (default: 50, max: 100)
Returned Params
- orders: Array
- id: String | uuid
- status: String
- created_at: DateTime
- updated_at: DateTime
- location: String | location short_name, or
nullfor orders with no location (e.g. domain-registration orders) - account: Object
- id: String
- name: String
View an Order
GET /api/orders/:id
Poll this endpoint until status reaches a terminal value (completed,
failed, cancelled).
Returned Params
- order: Object
- id: String | uuid
- status: String
- created_at: DateTime
- updated_at: DateTime
- location: String | location short_name, or
nullfor orders with no location (e.g. domain-registration orders)
- tasks: Array
- id: Integer
- status: String
- site: Object | present when the order provisioned a site
- id: String
- name: String
- domain: String
- variant: String
- location: String
- ssh_data: Object |
nullwhen the site has no SFTP credentials- ipaddr: String
- username: String
- password: String
- port: Integer
- subscription: Object | present when the order has a backing subscription
- id: Integer | the subscription's internal numeric id — see the warning below
- status: String
- bucket_type: String
- bucket_year: Integer
- bucket_month: Integer
- stripe_id: String
- domain: Object | present when the order registered or transferred a domain
- name: String
- action: String |
registerortransfer - registration: Object | the domain registration record — see Domain Registration.
nullduring the async window, until the registrar call succeeds - provisioning_status: String | present only while
registrationisnull; mirrors the orderstatus
- payment: Object
- status: String |
pending,processing,succeeded,awaiting_authentication, orfailed— see the note below - method_type: String | the resolved Stripe payment-method type, or
null. Commonlycardorsepa_debit, but this is an open set — delayed bank debits alone includeus_bank_account,acss_debit,bacs_debit,au_becs_debitandcustomer_balance, and others such asidealare possible. Treat any unrecognized value as valid - hosted_invoice_url: String | null
- amount_charged_cents: Integer |
nulluntil the Stripe invoice has been mirrored - credit_applied_cents: Integer |
nulluntil the Stripe invoice has been mirrored
- status: String |
- account: Object | See accounts#show
- user: Object | See users#show
subscription.id is not the id /api/subscriptions/:id accepts
The subscription.id returned here is the subscription's internal numeric
id, while GET /api/subscriptions/:id
looks the subscription up by its guid — the same value that
List Subscriptions returns as id.
Feeding this id straight into the subscriptions endpoint returns 404 with no
explanation. Match the subscription by another field, or list the account's
subscriptions and pick it up from there.
payment.status here has five values, not three
On order show, payment.status is derived from the order's own status: it may
be pending (nothing written to the payment intent yet), processing (charge
in flight), succeeded (fully paid), awaiting_authentication (3DS/SCA
challenge parked for recovery — direct the user to hosted_invoice_url), or
failed (declined, bounced, or cancelled). The 202 cart envelope returned by
POST /api/orders uses a different, three-value enum
for the same key — do not reuse one parser for both.
Order a new Site
POST /api/orders
Charges the billing account's default payment method off-session and
asynchronously provisions a WordPress site. There is no synchronous success
branch — the endpoint always returns 202 Accepted with a polling envelope.
Location and plan names can be found by querying the /api/about endpoint.
Pre-flight gate
Requires the billing account to be ready to charge — a Stripe customer ID, a
saved default payment method, and a complete billing contact. Failing the
gate returns 400 with code: "no_default_payment_method" and no order is
created. Accounts on a billing plan that does not settle through Stripe are
invoiced externally and skip this gate entirely, so they never return
no_default_payment_method.
Params
- site: Object (required)
- name: String | accepted but not used — see the note below
- variant: String (required) | vanilla, extendify
- location: String (required) | Location short_name (e.g. "pdx")
- plan: String (required) | Product short_name (e.g. "basic")
- term: String (required) | monthly or annual
- callback: Object | optional outbound notification POSTed when the order's async task completes — see Callbacks
- authorization: String | full Authorization header value. Example:
Bearer 12345 - url: String | fully qualified URL
- authorization: String | full Authorization header value. Example:
You cannot name the site
site.name is accepted by the endpoint and then ignored. The site is named
after a domain ordered in the same request; when no domain is ordered, it gets
a generated label built from the account's machine name. Read the site's real
name back from the order once it has provisioned.
Returned Params (always 202 Accepted)
- status: String |
"accepted" - cart: Object
- token: String
- status: String |
active,processing,checked_out - rollup_status: String |
pending,processing,completed, ... - poll_url: String | absolute URL, e.g.
https://your-instance/api/carts/<token>
- payment: Object
- status: String |
processing,succeeded,awaiting_authentication - method_type: String | the resolved Stripe payment-method type, or
null. Commonlycardorsepa_debit, but this is an open set — delayed bank debits alone includeus_bank_account,acss_debit,bacs_debit,au_becs_debitandcustomer_balance, and others such asidealare possible. Treat any unrecognized value as valid - hosted_invoice_url: String | null
- status: String |
- orders: Array
- id: String | uuid
- status: String |
pending - poll_url: String | absolute URL, e.g.
https://your-instance/api/orders/<id>
Poll cart.poll_url (or each order's poll_url) until the orders materialize
and reach a terminal state. orders is empty during the async window — the
order id is discovered by polling the cart.
Payment status enum (cart envelope)
In this envelope payment.status has exactly three values: processing (PI in
flight / off-session charge), succeeded (finalized), or
awaiting_authentication (3DS/SCA required or a recoverable decline — direct
the user to hosted_invoice_url). For SEPA, the method confirms as
processing and settles in 1–5 business days.
GET /api/orders/:id uses a wider five-value enum for the
same key.
On a billing plan that does not settle through Stripe the envelope's
payment.status stays processing and never reaches succeeded — the
terminal signal for those accounts is orders[].status reaching completed.
Errors
Every 4xx carries a machine-readable code alongside errors. Nothing is
charged and no order is created.
- 400
missing_account| theX-Auth-Accountheader is absent - 400
account_cannot_order| the account is not permitted to create orders - 400
unknown_variant|variantis not a known variant type - 400
unknown_location|locationis not a known public location - 400
over_capacity| the variant has no inventory at that location — retryable - 400
unknown_term|termis notmonthlyorannual - 400
unknown_product|planis not orderable on the account's billing plan - 400
no_price_for_plan| no price forplanat the requestedterm - 400
item_add_failed| the cart rejected the site item - 400
no_default_payment_method| billing account not ready to charge - 422
cart_pay_failed| the off-session charge could not be initiated
Payment could not be initiated (422)
If the cart is valid but Cart::PayService fails to start the off-session
charge, the response is 422 with code: "cart_pay_failed". (A pre-flight
failure is 400; a payment-initiation failure is 422.)
Register or Transfer a Domain
POST /api/orders/domain
Registers or transfers a domain through the same cart/billing flow as
POST /api/orders, charging the account's default saved payment method
off-session, then asynchronously registering (or transferring) the domain and
creating a renewal subscription and DNS zone. Always returns 202 Accepted with
the shared cart envelope; poll /api/orders/:id (which carries a domain block)
until status is terminal.
Not available via OAuth and requires a user-scoped credential. A
system Account-bearer API key (no user) returns 401 user_required.
Requires the X-Auth-Account header and the domain-registration
feature flag — when the flag is off, returns 503 feature_disabled. Trial
accounts return 403 trial_account.
Params
- domain: Object (required)
- name: String (required) | fully-qualified, e.g.
example.com - action: String (optional) |
register(default) ortransfer - authcode: String | required for transfers when the TLD requires an authcode
- contact: Object | inline registrant; required unless
contact_sourceis given- first_name: String (required)
- last_name: String (required)
- organization: String (optional)
- street_address: String (required)
- post_code: String (required)
- city: String (required)
- state: String (optional/required per TLD)
- country: String (required) | 2-letter ISO code
- email: String (required)
- phone: String (required) | E.164, e.g.
+1.5555555555 - extra_properties: Object | TLD-specific registry fields (e.g.
.itentityType)
- contact_source: Object | reuse an owned registration's contacts instead of
contact- registration_id: Integer (required) | an owned DomainRegistration that has an owner contact
- role_contacts: Object (optional) |
admin/billing/tech; each:- source: String |
same_as_owner(default),existing, ornew - source_registration_id: Integer | when
sourceisexisting - first_name, last_name, ... | the same contact fields as above, when
sourceisnew
- source: String |
- name: String (required) | fully-qualified, e.g.
- callback: Object (optional) | same callback contract as
POST /api/orders— see Callbacks- authorization: String | full Authorization header value. Example:
Bearer 12345 - url: String | fully qualified URL
- authorization: String | full Authorization header value. Example:
Provide a registrant via either an inline contact object or
contact_source.registration_id (to reuse the contacts of an owned
registration), not both.
Returned Params (always 202 Accepted)
Identical envelope to POST /api/orders:
- status: String |
"accepted" - cart: Object |
{ token, status, rollup_status, poll_url } - payment: Object |
{ status, method_type, hosted_invoice_url } - orders: Array |
[{ id, status, poll_url }](empty during the async window)
Errors
Authorization / gating:
- 401
user_required| system Account-bearer key (no user) - 503
feature_disabled| domain registration not enabled - 403
trial_account| trial accounts cannot order domains
Validation (400, no charge / no order created):
missing_account| theX-Auth-Accountheader is absentaccount_cannot_order| the account is not permitted to create ordersinvalid_action|actionnotregisterortransferinvalid_domain| name failed normalization/validationunsupported_tld| no configured TLD for the namepricing_unavailable| no orderable price for the account's billing planauthcode_required| transfer of a TLD that requires an authcode, none suppliedincomplete_contact| inline contact missing required fieldsinvalid_contact| inline contact has a bad value (e.g.countrynot 2 letters)invalid_contact_source|contact_source.registration_idnot found or has no owner contactno_default_payment_method| billing account not ready to charge
Processing (422):
domain_unavailable| registry says the name is not registerable (register only)domain_availability_unknown| the availability check itself failed, so we cannot say whether the name is registerable — retry (register only)domain_add_failed| cart rejected the domain (e.g. already owned)domain_contact_invalid| registrant contact rejected (body carriesviolations)domain_contact_needs_augmentation| TLD needs extra contact fields (body carriesneeds_augmentation)cart_pay_failed| payment could not be initiated (any contacts created for this request are torn down)
domain_unavailable is not the same as domain_availability_unknown
Only domain_unavailable is a statement about the name — the registry said it
cannot be registered. domain_availability_unknown means the availability
check itself failed, so nothing is known about the name: retry rather than
telling the customer it is taken or offering them alternatives.
Cancel an Order
Cancels an in-progress order.
DELETE /api/orders/:id
Returns 202.
PATCH is a no-op
PATCH /api/orders/:id is reserved and returns 400.
Carts
Poll cart-level state during the async window after POST /api/orders, a site
plan change, or any other paid endpoint that
answers with the same cart envelope.
Account Scope Required (X-Auth-Account). Scope: billing:read. Carts are
scoped to your own account; an unknown token returns 404.
GET /api/carts/:token
The cart token comes from the create response (cart.token / cart.poll_url).
The response shape mirrors POST /api/orders so you can reuse the same parser:
Returned Params
- status: String |
"accepted" - cart: Object
- token: String
- status: String
- rollup_status: String
- poll_url: String
- payment: Object
- status: String
- method_type: String | the resolved Stripe payment-method type, or
null. Commonlycardorsepa_debit, but this is an open set — delayed bank debits alone includeus_bank_account,acss_debit,bacs_debit,au_becs_debitandcustomer_balance, and others such asidealare possible. Treat any unrecognized value as valid - hosted_invoice_url: String | null
- orders: Array
- id: String | uuid
- status: String
- poll_url: String