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
- ipaddr: String
- username: String
- password: String
- port: Integer
- subscription: Object | present when the order has a backing subscription
- id: String
- status: String
- bucket_type: String
- bucket_year: Integer
- bucket_month: Integer
- stripe_id: String
- payment: Object
- status: String | see payment status enum below
- method_type: String |
card,sepa_debit, or null - hosted_invoice_url: String | null
- amount_charged_cents: Integer
- credit_applied_cents: Integer
- account: Object | See accounts#show
- user: Object | See users#show
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.
Params
- site: Object (required)
- name: String (required)
- 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:
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 |
/api/carts/<token>
- payment: Object
- status: String |
processing,succeeded,awaiting_authentication - method_type: String |
card,sepa_debit, or null - hosted_invoice_url: String | null
- status: String |
- orders: Array
- id: String | uuid
- status: String |
pending - poll_url: String |
/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
payment.status may be 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.
Pre-flight / validation errors (400)
no_default_payment_method| billing account not ready to charge- unknown
variant/location/term/plan - variant not available (over capacity)
- missing pricing information
"Account unable to create orders."
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):
invalid_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_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)
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 or a site
plan change.
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 |
card,sepa_debit, or null - hosted_invoice_url: String | null
- orders: Array
- id: String | uuid
- status: String
- poll_url: String