Everything Teculiar does, reachable from a website we do not host: browse the catalog, search domains, check out, pay invoices, add funds, manage services, open and answer tickets, read content, manage an account.

Base URL: https://api.<your-domain>/api/v1/public/… (or https://<tenant>.teculiar.net/api/v1/public/…). Generated reference: openapi/public-v1.json.

You wantRead
A JS SDK or drop-in widgetssdk-widgets.md
Event notifications to your serverwebhooks.md
Every route, internal ones includedapi-endpoints.md
How the contract is enforced in CIapi-contract.md

It is a projection, not a copy

The public API is not a hand-maintained parallel implementation. Every route in the product carries an explicit @PublicRoute or @InternalRoute decision, the API refuses to boot if one does not, and /api/v1/public/* is a second door onto the very same handlers. The OpenAPI spec, the SDK and this documentation are generated from those decisions.

That matters to you as a consumer: there is no second implementation to fall behind, so a fix to the product is a fix to the public API on the same day.

Authenticating

Two things authenticate a call, and they answer different questions.

What it provesWhere it lives
API key (Teculiar-Key)which site is callingpk_ in page source, sk_ on your server
Customer token (Authorization: Bearer)which person the data belongs toobtained from /public/auth/login

A key authorizes the site; a token authorizes the person. A publishable key may hold invoices:read, but every route carrying that scope also demands a customer token — so the key alone never reads anyone's data.

pk_ publishable

An identifier, not a secret. It sits in page source by design, so the control is its **origin allowlist**: a request from an origin you have not registered is a 403. Register origins at Admin → Settings → API keys.

sk_ secret

Server-side only. A request carrying an Origin header is refused outright — an Origin means a browser sent it, and a secret key in a browser has already leaked. Rotate it if you see that error.

Unknown, revoked and expired keys all fail identically, so probing tells you nothing.

Scopes

Three groups. The axis is not "is the data public" but "is access gated by something other than the key".

Anonymous — the key is the only gate, so the data behind it is public: catalog:read, content:read, domains:search, inquiries:write, auth:write, checkout:write, payment-methods:read.

Customer — a pk_ may hold these, but every route carrying one also requires a customer token: customers:read|write, invoices:read|pay, orders:read, payments:write, services:read|manage, domains:manage, tickets:read|write.

Secret — never holdable by a pk_: webhooks:manage.

**Credentials never answer a pk_.** A route that hands out a credential — a domain's transfer code (GET /services/:id/transfer-code, and POST /services/:id/domain-lock when it unlocks), or a secret-class module action — needs the customer token sent with an sk_ key from your server. With a pk_ it is a 403, whatever scopes the key holds: a script injected into a page that carries a pk_ would hold the customer's token too.

Idempotency

Send an Idempotency-Key header (1 to 200 characters of A-Z a-z 0-9 - _, a new value for each intended request) on POST /orders/checkout, POST /orders/{id}/pay, POST /billing/invoices/{id}/pay, POST /billing/add-funds, POST /storefront/inquiries and POST /tickets. The operations that take it are marked x-idempotent in the spec.

  • The same key and the same body again returns the first answer, with the same status, and the header Idempotent-Replayed: true. Nothing runs twice: no second order, account, invoice or charge.
  • The same key with a different body (or from a different customer) is 422 IDEMPOTENCY_KEY_REUSED.
  • The same key while the first request is still being handled is 409 IDEMPOTENCY_IN_PROGRESS; retry after a moment. A request that never finished (the server restarted) is forgotten after five minutes.
  • An answer of 2xx or 4xx is kept for 24 hours. A 5xx, or a crash, leaves nothing behind, so the retry runs. Only a hash of your request body is stored, never the body; the stored answer is the one you were sent.
  • Without the header nothing changes. A malformed key is 400 IDEMPOTENCY_KEY_INVALID.

The SDK sends a fresh key on each call of those operations and repeats the call with the same key after a timeout or a 5xx. It never repeats any other write.

Rate limits

Per key and per IP, enforced globally. The tenant-facing numbers are settings under Admin → Settings → Integrations, each capped by a platform ceiling that protects the platform rather than the tenant — domain search is the strict one, because every search costs outbound lookups at the registries from our IP address.

The ceiling itself is operator-side, stored in the control-plane and unreachable from any tenant-facing route. Your admin shows you the ceiling beside each field so a clamped value is explained rather than mysterious; raising one is a conversation with us, not a setting.

A 429 carries Retry-After. The SDK surfaces it as a typed error rather than retrying blindly.

Compatibility

The published surface is additive-only. Fields are added, never removed or renamed without a version bump — and that is enforced, not promised: scripts/api-contract-check.ts diffs the generated spec against the last released snapshot and fails the build when a published field disappears.

A Teculiar-Version date header is reserved for a future breaking change. Until one exists, there is nothing to pin.

Request inputs. docs/openapi/public-v1.json carries each write operation's requestBody and each operation's query parameters, written in public-inputs.ts and checked against what the handlers actually read. The gate also refuses, against the last release, a removed input, an optional input made required, a new required input, and a removed allowed value (an enum). Fields marked required in the spec are the ones the API refuses a request without.

Response types. A field in the spec carries a JSON type where the shape says one (string, integer, number, boolean, object, array, and null for fields that can be empty); the SDK's types follow, so a call comes back typed. The catalogue, the bootstrap, the order preview and checkout, the status route, posts, the module library, the name check and the plans are typed in full. Every published field is still optional in the SDK's type, and a type is part of the contract like a name.

Caching. Anonymous reads carry an ETag and Cache-Control: public, max-age=60. Anything requiring a customer token is private, no-store — a per-customer response must never sit in a shared cache.

Bootstrap: GET /storefront/i18n

One small cached call tells a site everything it needs for languages and money. Scope catalog:read, no sign-in; the public cache keeps it for 60 seconds.

{
  "main": "en",
  "languages": ["en", "de"],
  "installed": ["de", "en", "tr"],
  "fallback": "en",
  "meta": {
    "en": { "bcp47": "en-US", "nativeName": "English", "flag": "us", "defaultCurrency": "USD" },
    "de": { "bcp47": "de-DE", "nativeName": "Deutsch", "flag": "de", "defaultCurrency": "EUR" }
  },
  "currencies": {
    "main": "USD",
    "list": ["USD", "EUR"],
    "rates": { "EUR": { "rate": 0.92, "buffer": 0, "bufferEnabled": false } }
  },
  "serverTime": 1760520000000,
  "cookieDomain": null
}
  • languages is what the store serves, main first. meta has one entry per served language (not per installed pack). defaultCurrency is the currency the language implies, taken the way the Teculiar themes take it: the language pack's own, else the main language's. A site uses it only when the store lists that currency.
  • currencies.list is main first. A currency without a valid exchange rate (a number above 0) is never listed. rates holds one entry for each listed non-main currency. Rates and buffers are display data, the same as in /storefront/settings; the server still prices every order.
  • serverTime is the server's clock in milliseconds when the answer was built. The answer is cached, so it can be a few minutes old; the SDK corrects for that.
  • cookieDomain is null until a tenant registers its own website's host (parity L4).

Platform-only routes

These answer on teculiar.com (and on the platform's own lab); on any other store they are 404.

  • GET /platform/names/check?name= (scope catalog:read, 30 a minute) says whether a system name (<name>.teculiar.net) can be claimed: { name, available, reason, suggestions, rules }. reason is null, invalid, reserved, not_for_sale, taken (a system has it) or held (another unpaid order is paying for it). suggestions (at most 3) appear for a valid name that is not available. rules is the grammar: 1 to 31 characters, letters, digits and hyphens, not starting or ending with a hyphen.
  • POST /orders/preview and POST /orders/checkout refuse a name that is not available with 409 and { code: "NAME_UNAVAILABLE", name, reason }. Checkout holds the name until the invoice is due, so the name a buyer pays for is the name they get. The hold ends when the invoice is deleted or the system exists.
  • GET /storefront/modules?locale= and GET /storefront/modules/{name}?locale= list the module library: name, label and description in that language (English when there is no translation), kind, families, capabilities, pricing (free or paid) and hasPanel. The single route adds doc: the module's guide, { language, markdown }, or null. Platform-only modules are never listed.
  • GET /platform/plans?locale= (scope catalog:read) lists the four Teculiar plans from the shared registry: key, name, summary (asked language, English fallback), currency, monthlyCents, yearlyCents, limits, features, ceilingProfile. The pricing page reads it; no limit is typed by hand.

Add-ons in the catalogue

GET /storefront/products gives each product an addOns list (active ones only): id, slug, name, nameTranslations, description, descriptionTranslations, amountCents, setupFeeCents, billingCycle, recurring and maxQuantity (99). Send the id and a quantity in an order line's addOns: [{ addOnId, quantity }] at preview and checkout. What buying one changes on the service is internal and is not published.

Order fields

A product whose module asks the buyer for something (a Teculiar plan: system name, brand, administrator name and email) lists it in configurationFields on GET /storefront/products: key, type (text, email or subdomain), required, maxLength, pattern, label and help for each served language, and checkUrl (the route that says whether a subdomain is free, otherwise null). Send the values in the order line's configuration under those keys. Preview and checkout refuse a line that misses a required field or breaks pattern or maxLength with 400 and { code: "ORDER_FIELD_INVALID", field: "<key>", reason } (required, invalid or too_long).

Checkout status

POST /orders/checkout answers with the checkout as the invoice behind it and a status token:

{ "invoice": { "id": "…", "invoiceNumber": "N-100821", "status": "PENDING", "currency": "EUR", "totalCents": 741, "dueAt": "…" },
  "order": { "id": "…" },
  "statusToken": "…" }

order.id is the checkout id (the invoice id) until payment creates the order. The statusToken is shown once; only its hash is kept. Pay with POST /orders/{id}/pay (the answer carries invoice.paymentRedirectUrl when the gateway needs the buyer, and invoice.attemptStatus when an attempt failed, was cancelled or expired).

A guest's page follows the checkout without signing in: GET /orders/{id}/status?token=<statusToken> (scope checkout:write, 60 requests a minute).

{ "status": "provisioning",
  "items": [ { "type": "MANAGED_SERVICE", "productName": "Branded", "status": "pending", "externalId": null, "links": [] } ] }

status is awaiting_payment, provisioning (paid, something is not active yet), ready (every item active), failed or cancelled. An item is pending, active or failed; once active it carries the service's externalId and a portal link to its page. No names, emails, addresses or amounts are in this answer. A wrong token and an unknown checkout are the same 404.

Return URLs

returnUrl and cancelUrl on payment calls are validated against the hosts you have verified plus the origins registered on the calling key. Anything else is a hard 400, never a silent fallback.

This is strict on purpose. An open redirect on a payment flow is a phishing gift: it lets an attacker send your customer to a lookalike checkout immediately after a real payment, on a link that genuinely came from you. Host equality on a parsed URL — shop.example.com.evil.test and shop.example.com@evil.test are both refused.

Where the buyer goes, and when. returnUrl and cancelUrl are accepted by POST /orders/{id}/pay and POST /billing/invoices/{id}/pay. Neither is given to the payment gateway: the gateway always returns to the store's own payment-return page, which captures the payment (PayPal, Mollie) and creates the order, and only then sends the buyer on. Your address is stored with the checkout and used after that. It gets two query parameters added, order (the checkout id) and result, one of paid, pending (for example SEPA, still being processed), failed or cancelled. A cancelled payment goes to cancelUrl when you gave one, otherwise to returnUrl. The address never carries a login code or token: the buyer's sign-in stays on the store's page. Without returnUrl the buyer stays on the store's own pages, as before. Both returnUrl and cancelUrl are checked the same way.

The framed checkout has no key. <teculiar-checkout> pays from inside the store's own frame, so no key's origins apply: its return-url and cancel-url must be on a host you verified (a *Your own website* host in Admin → Domains, or one of the store's own hosts). A site that is not verified gets the same 400, shown in the frame. Calls made with a key (the headless flow) may also use the key's origins.

Results by provider (checked live against Mollie's test mode and PayPal's sandbox): a paid payment arrives as result=paid; a payment the provider refused, or that expired, as failed; a buyer who presses back at Mollie, or "Cancel and return" at PayPal, as cancelled. Nothing is paid in the last two and the invoice stays payable.

Support — tickets and contact

Contact / inquiry form

POST /storefront/inquiries — no customer token required, no API key required (anonymous). Creates a ticket in the tenant's support queue and returns a ticket record.

POST /api/v1/public/storefront/inquiries
Content-Type: application/json

{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "subject": "Pre-sales question",
  "message": "Does your Starter plan include email hosting?",
  "source": "contact"
}

Response 201:

{
  "id": "cmt8duoue04z5qr012wlvgu7v",
  "publicId": "MED8RD8L",
  "status": "OPEN",
  "priority": "NORMAL",
  "subject": "Pre-sales question",
  "createdAt": "2026-08-25T08:08:14.150Z",
  "updatedAt": "2026-08-25T08:08:14.150Z",
  "replies": [
    {
      "id": "cmt8duoue04z7qr01jfv92stu",
      "body": "Name: Jane Smith\nE-Mail: jane@example.com\n\nDoes your Starter plan include email hosting?",
      "internal": false,
      "createdAt": "2026-08-25T08:08:14.150Z"
    }
  ]
}

Fields: name (1–100 chars), email, phone (optional, max 30), subject (1–200), message (10–5000). source is "contact" or "inquiry" — routes the ticket to the department the tenant has configured for each. _honey is a honeypot field; a non-empty value signals a bot and the submission is silently discarded.

Customer tickets

These routes require a customer bearer token (Authorization: Bearer <token>) and the tickets:read or tickets:write scope on the API key.

MethodPathScopeWhat it does
GET/ticketstickets:readList the authenticated customer's tickets. Optional ?status= and ?department=.
POST/ticketstickets:writeOpen a ticket. Required: subject, body, departmentId. Optional: priority, serviceId.
GET/tickets/departmentstickets:readList departments a ticket can be opened against (id/slug/name/colour).
GET/tickets/{id}tickets:readRead one ticket with replies. Accepts database id or 8-char public id. Internal notes and staff-only fields are stripped.
POST/tickets/{id}/repliestickets:writeReply to a ticket. internal field ignored for customer tokens.
POST/tickets/{id}/attachmentstickets:writeAttach files (multipart, field files). PNG/JPG/WebP/GIF/AVIF/TIFF and PDF (when the tenant allows it), max 5 files, 25–100 MB each by plan. Every file is re-encoded before storing. Each attachment in a response carries url, a signed link on the files host valid for 1 hour (expiresAt), or null with removedReason. storageKey is deprecated: it now holds the same absolute link. Throttled at 20/min.
POST/tickets/{id}/closetickets:writeClose a ticket.

priority values: LOW, NORMAL (default), HIGH, URGENT.

GET /tickets/departments is published because departmentId is a required field on POST /tickets — without it the write would be uncallable from the public API. The department's routing mailbox is not included in the response.

Internal notes (internal: true replies) are stripped server-side for customer tokens. A customer cannot post a staff note through a customer token even by setting internal: true in the body.

Staff-only routes (assign, status change, delete, canned replies, admin ticket queue) are @InternalRoute and are not published here.

What is deliberately not published

  • The admin surface. This phase makes the customer-facing product headless. Running the admin dashboard headlessly is a different product with a different threat model.
  • Module service panels (services/:id/panel) — the hosting control panel, a domain's transfer lock. They manage mailboxes and reset passwords. They render inside the embedded portal on our origin, or not at all. What a module chooses to publish of its panel is under Module actions.
  • **POST /services** — provisions a service with no order and no invoice behind it. The public path to a new service is orders/checkout, which prices it and raises the invoice.
  • Webhook endpoint CRUD. A key that could register a receiving URL is a data-exfiltration channel. Configure endpoints in your admin.
  • The SSO exchange/redeem pair. A host-pinned internal primitive, not a grant type.

Module actions

A service's module (its hosting panel, server or registrar) can publish some of its panel actions here. Each action has a risk class, and the class decides how far it may go:

ClassRouteScopeNotes
readGET /services/:id/actions/:actionservices:read
reversiblePOST /services/:id/actions/:action (a registrar's: POST /services/:id/domain-actions/:action)services:manage (domains:manage)30 a minute
secretsame as reversiblesamenever with a pk_ — see Scopes
destructive——never published

GET /services/:id/actions lists what the service's module publishes: id, label, module, risk, method, path, scope and the body fields it takes. An action the module does not publish on the route you called is a 404, like one that does not exist. The body is flat text fields; the action is named by the path.

ModulePublished
Hetzner Cloudpower-on, reboot, shutdown, power-off (reversible)
cPanelsso (field app: files, email, apps), webmail — single-use sign-on links (secret)
Resell.biz, Dynadotlock-state (read), lock (reversible), unlock (secret: it returns the transfer code)
Virtualminnothing

The answer is the provider's receipt — accepted, message, and url for a sign-on link — or, for a registrar, the lock (domain, locked, lockCheckedAt, transferCode after an unlock). Every run is in the tenant's logs; a secret action's answer never is.

Order line configuration

POST /orders/checkout and /orders/preview accept a free-form configuration object on each line, but only these keys are kept — anything else is dropped without an error:

ForKeys
A domain linebillingCycle, domainAction, extraAttributes, nameServers, transferAuthCode
A hosting line and its domainbundledDomain, domainName, domainUse
A Teculiar planadminEmail, adminName, brand, subdomain

Everything that decides what gets provisioned or billed — the hosting plan, the renewal price, a free domain, the provisioning module — comes from the product and the server, never from the request. The list lives in apps/api/src/common/order-line-configuration.ts.

Service lifecycle

A service object's status field is the customer's view of its state. The values form a linear lifecycle; branches only happen at ACTIVE:

StatusMeaning
PENDINGCreated and waiting for payment to clear, or waiting for the hosting module to confirm provisioning.
PROVISIONINGPayment cleared; the hosting module is actively setting up the account. (Virtualmin internally returns "QUEUED" while its job queue processes, which the platform maps to PROVISIONING before writing.)
ACTIVERunning normally.
SUSPENDEDSuspended for non-payment. Restored automatically when the overdue invoice is settled.
CANCELLEDScheduled for termination at cancelAt, or already cancelled. Renewal invoices stop.
TERMINATEDPermanently deprovisioned. The account has been removed from the provider.
FAILEDProvisioning ran and did not succeed; retryable by admin.

Suspension grace. An invoice that goes overdue does not suspend the service immediately. The dunning sweep (run once per day by cron) suspends only when a full 24 hours have elapsed since the invoice dueAt. A hosting service (Virtualmin) is additionally disabled at the panel on suspension — the account is not deleted and is re-enabled on payment.

Cancellation is not immediate. POST /services/:id/cancel schedules the end of service at the end of the paid term (renewsAt, falling back to nextDueAt). The cancelAt field in the response carries that date; the service stays ACTIVE until the cron sweep reaches it. POST /services/:id/resume undoes the cancellation if called before that date.

Field strip on the public surface. The service object returned by GET /services and GET /services/:id deliberately omits:

  • configuration / customFields — opaque provisioning blobs, may carry hosting credentials.
  • moduleName / moduleReference / externalId — internal infrastructure identifiers.
  • suspendedReason — an internal admin note.
  • userId, orderId, orderItemId, initialInvoiceId — foreign keys.

The domainRecords[] sub-array omits eppCode, authCodeHash, registrarProvider, registrarModule, externalId, and dnsRecords for the same reason.

CORS

/api/v1/public/* answers Access-Control-Allow-Origin: * with credentials: false, and accepts Content-Type, Authorization and Teculiar-Key.

credentials: false alongside Authorization is correct rather than contradictory: a bearer token in an explicit header is not a CORS credential, because the browser never attaches it on its own. Only a cookie would be, and this surface reads none.

CORS is not the access control — a non-browser caller ignores it entirely. The control is the key guard.

Errors

Standard HTTP status codes with a JSON body.

StatusMeans
400The request is malformed, or a return URL failed validation.
401Missing/invalid key, or a customer token is required and absent.
403The key lacks the scope, or the origin is not allowed.
404Not published, or genuinely missing — deliberately indistinguishable.
409A payment for this invoice is already under way, or the invoice has since been paid.
429Rate limited. See Retry-After.

409 comes from POST /billing/invoices/{id}/pay and is the one status worth handling specially: the request was fine and there is nothing to correct, so a client must not retry it with another payment method. One invoice is collected once, and a payment already in flight at a gateway — a SEPA direct debit takes days to clear — refuses a second one whatever method it names. Show the message to the buyer and stop; the invoice moves to paid on its own when the money arrives. See docs/ordering-and-invoices.md.

Login returns an identical 401 whether the address exists or the password is wrong, and password reset always returns {ok: true}. Registration still reveals that an address is taken — a deliberate UX trade, pre-existing on the hosted storefront, mitigated by a hard throttle rather than claimed to be safe.

Operations

auth

POST /auth/2fa/disable

Turn two-factor off

ScopeSign-inSince
customers:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
passwordbodystringYes

GET /auth/2fa/setup

Begin two-factor setup

ScopeSign-inSince
customers:writeCustomer sign-in2026-09

GET /auth/2fa/status

Whether two-factor is enabled

ScopeSign-inSince
customers:readCustomer sign-in2026-09

POST /auth/2fa/verify

Confirm two-factor setup

ScopeSign-inSince
customers:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
codebodystringYes

POST /auth/login

Sign a customer in and return bearer tokens

ScopeSign-inSince
auth:writeKey only2026-09
NameInTypeRequiredNotes
emailbodystring emailYes
passwordbodystringYes
scopebodyadmin / client
totpCodebodystringThe six digits from the authenticator, when two-factor is on.

POST /auth/logout

Revoke a refresh token

ScopeSign-inSince
auth:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
refreshTokenbodystringYes

POST /auth/password-reset/confirm

Complete a password reset

ScopeSign-inSince
auth:writeKey only2026-09
NameInTypeRequiredNotes
passwordbodystringYes
tokenbodystringYes

POST /auth/password-reset/request

Request a password-reset email

ScopeSign-inSince
auth:writeKey only2026-09
NameInTypeRequiredNotes
emailbodystring emailYes
scopebodyadmin / client

POST /auth/refresh

Exchange a refresh token for a new access token

ScopeSign-inSince
auth:writeKey only2026-09
NameInTypeRequiredNotes
refreshTokenbodystringYes

POST /auth/register

Register a customer account

ScopeSign-inSince
auth:writeKey only2026-09
NameInTypeRequiredNotes
addressbodyobject
companyNamebodystring
countryCodebodystring
customerTypebodyINDIVIDUAL / BUSINESS
emailbodystring emailYes
namebodystringYes
passwordbodystringYes
phonebodystring
vatIdbodystring

billing

POST /billing/add-funds

Top up the customer's account balance

ScopeSign-inSince
payments:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
amountCentsbodyintegerYes
ibanbodystring
methodbodyCREDIT_CARD / PAYPAL / SEPA / CRYPTO / BANK_TRANSFER / IDEALYes

GET /billing/invoices

List the customer's invoices

ScopeSign-inSince
invoices:readCustomer sign-in2026-09
NameInTypeRequiredNotes
statusquerystringOnly invoices in this status.

GET /billing/invoices/{id}

Read one invoice

ScopeSign-inSince
invoices:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

GET /billing/invoices/{id}/html

Render an invoice as HTML

ScopeSign-inSince
invoices:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

POST /billing/invoices/{id}/pay

Pay an invoice

ScopeSign-inSince
invoices:payCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
cancelUrlbodystringWhere the buyer ends up after a cancelled payment. Same rule as returnUrl.
ibanbodystring
methodbodyCREDIT_CARD / PAYPAL / SEPA / CRYPTO / BANK_TRANSFER / IDEALYes
paymentMethodIdbodystring
returnUrlbodystringWhere the buyer ends up after the payment is confirmed, with order and result added. Must be a verified host of the store or an origin of the key.
useAccountBalancebodyboolean

GET /billing/invoices/{id}/payment-methods

List the payment methods that can pay one invoice

ScopeSign-inSince
invoices:payCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /billing/invoices/{id}/pdf

Download an invoice PDF

ScopeSign-inSince
invoices:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /billing/payment-methods

List the customer's saved payment methods

ScopeSign-inSince
payment-methods:readCustomer sign-in2026-09

cms

GET /cms/announcements

List announcements for the signed-in customer

ScopeSign-inSince
customers:readCustomer sign-in2026-09
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

POST /cms/announcements/{id}/hide

Hide an announcement for this customer

ScopeSign-inSince
customers:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /cms/announcements/{id}/read

Mark an announcement as read

ScopeSign-inSince
customers:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

GET /cms/blog-categories

List blog categories

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /cms/blog-tags

List blog tags

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /cms/pages/{locale}/{slug}

Read a published CMS page

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
localepathstringYes
slugpathstringYes

GET /cms/post-tags

List blog post tags

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
limitquerystringHow many tags, most used first.
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /cms/posts

List published blog posts

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
tagquerystringTag slug.
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /cms/posts/{locale}/{slug}

Read a published blog post

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
localepathstringYes
slugpathstringYes

domains

Check domain availability and price

ScopeSign-inSince
domains:searchKey only2026-09
NameInTypeRequiredNotes
domainquerystringYesThe name to look up, with its extension.
yearsquerystringRegistration years to price.
suggestionsquerytrue / falsefalse skips the other-extension suggestions (default true).

knowledgebase

GET /knowledgebase

Read published knowledgebase articles

ScopeSign-inSince
content:readKey only2026-09

GET /knowledgebase/{slug}

Read published knowledgebase articles

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
slugpathstringYes

GET /knowledgebase/suggest

Read published knowledgebase articles

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
qquerystringWhat the visitor has typed so far.

orders

GET /orders/{id}

Read one order

ScopeSign-inSince
orders:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /orders/{id}/pay

Pay for an order

ScopeSign-inSince
payments:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
cancelUrlbodystringWhere the buyer ends up after a cancelled payment. Same rule as returnUrl.
ibanbodystring
methodbodyCREDIT_CARD / PAYPAL / SEPA / CRYPTO / BANK_TRANSFER / IDEALYes
paymentMethodIdbodystringYes
returnUrlbodystringWhere the buyer ends up after the payment is confirmed, with order and result added. Must be a verified host of the store or an origin of the key.

GET /orders/{id}/status

Read a checkout's progress with its status token

ScopeSign-inSince
checkout:writeKey only2026-10
NameInTypeRequiredNotes
idpathstringYes
tokenquerystringYesThe statusToken that checkout answered with.

POST /orders/checkout

Place an order (guest or signed-in)

ScopeSign-inSince
checkout:writeKey only2026-09
NameInTypeRequiredNotes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
currencybodystringISO 4217 code the buyer is charged in; an unserved one falls back to the store's main currency.
customerbodyobjectYes
customer.addressbodyobject
customer.companyNamebodystring
customer.countryCodebodystring
customer.customerTypebodyINDIVIDUAL / BUSINESS
customer.emailbodystring emailYes
customer.namebodystringYes
customer.passwordbodystringFor a new account.
customer.phonebodystring
customer.vatIdbodystring
itemsbodyobject[]Yes
items[].addOnIdsbodystring[]Add-ons, one of each. addOns wins when both are sent.
items[].addOnsbodyobject[]Add-ons with how many of each.
items[].addOns[].addOnIdbodystring
items[].addOns[].quantitybodyinteger
items[].configurationbodyobjectValues for the product's order fields.
items[].domainNamebodystringFor a domain line: the name to register or transfer.
items[].productIdbodystring
items[].productPriceIdbodystringThe price (billing cycle) to buy; the cheapest is used when absent.
items[].quantitybodyinteger
localebodystringLanguage code, e.g. en or de. Names and messages come back in it.

POST /orders/preview

Price an order server-side before checkout

ScopeSign-inSince
checkout:writeKey only2026-09
NameInTypeRequiredNotes
currencybodystringISO 4217 code the buyer is charged in; an unserved one falls back to the store's main currency.
customerbodyobjectOnly what a price depends on.
customer.countryCodebodystring
customer.customerTypebodyINDIVIDUAL / BUSINESS
customer.vatIdbodystring
itemsbodyobject[]Yes
items[].addOnIdsbodystring[]Add-ons, one of each. addOns wins when both are sent.
items[].addOnsbodyobject[]Add-ons with how many of each.
items[].addOns[].addOnIdbodystring
items[].addOns[].quantitybodyinteger
items[].configurationbodyobjectValues for the product's order fields.
items[].domainNamebodystringFor a domain line: the name to register or transfer.
items[].productIdbodystring
items[].productPriceIdbodystringThe price (billing cycle) to buy; the cheapest is used when absent.
items[].quantitybodyinteger
localebodystringLanguage code, e.g. en or de. Names and messages come back in it.

platform

GET /platform/names/check

Check whether a system name can be claimed

ScopeSign-inSince
catalog:readKey only2026-10
NameInTypeRequiredNotes
namequerystringThe system name to check, as typed.

GET /platform/plans

List the Teculiar plans with their prices, limits and features

ScopeSign-inSince
catalog:readKey only2026-10
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

products

GET /products

List products

ScopeSign-inSince
catalog:readKey only2026-09

services

GET /services

List the customer's services

ScopeSign-inSince
services:readCustomer sign-in2026-09

GET /services/{id}

Read one service, including its domain records

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

GET /services/{id}/actions

List the module actions a service offers

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

GET /services/{id}/actions/{action}

Run a read action of a service's module

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
actionpathstringYes

POST /services/{id}/actions/{action}

Run an action of a service's module

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
actionpathstringYes

PATCH /services/{id}/auto-renew

Turn a service's auto-renew on or off

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
autoRenewbodybooleanYes

POST /services/{id}/cancel

Request cancellation of a service

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
cancelDomainsbodybooleanAlso cancel the domains on the service.
reasonbodystring
reasonTextbodystring
timingbodystringWhen the cancellation takes effect.

GET /services/{id}/cancellation-request

The cancellation request waiting on a service, if any

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /services/{id}/change-plan

Change a service's plan

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
productPriceIdbodystringThe price to move to, from plan-options.

POST /services/{id}/domain-actions/{action}

Run an action of a domain's registrar module

ScopeSign-inSince
domains:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
actionpathstringYes

GET /services/{id}/domain-lock

Read the transfer lock on a domain service

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /services/{id}/domain-lock

Lock or unlock a domain for transfer

ScopeSign-inSince
domains:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
lockedbodybooleanYes

GET /services/{id}/plan-options

Plans this service can change to

ScopeSign-inSince
services:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /services/{id}/renew-domain

Renew the domain attached to a service

ScopeSign-inSince
domains:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
yearsbodyintegerYes

POST /services/{id}/restart

Restart a service

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /services/{id}/resume

Withdraw a cancellation request, or undo a scheduled cancellation

ScopeSign-inSince
services:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

GET /services/{id}/transfer-code

Read the transfer (EPP) code for an unlocked domain

ScopeSign-inSince
domains:manageCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

storefront

GET /storefront/domain-prices

List TLD registration and transfer prices

ScopeSign-inSince
catalog:readKey only2026-09

GET /storefront/embed

Read the embed origin, feature toggles and brand theme for this tenant

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
brandquerystringUsed only where the store has saved no value of its own.
accentquerystringUsed only where the store has saved no value of its own.
onAccentquerystringUsed only where the store has saved no value of its own.
backgroundquerystringUsed only where the store has saved no value of its own.
surfacequerystringUsed only where the store has saved no value of its own.
textquerystringUsed only where the store has saved no value of its own.
mutedquerystringUsed only where the store has saved no value of its own.
linequerystringUsed only where the store has saved no value of its own.
dangerquerystringUsed only where the store has saved no value of its own.
successquerystringUsed only where the store has saved no value of its own.
radiusquerystringUsed only where the store has saved no value of its own.
fontquerystringUsed only where the store has saved no value of its own.
densityquerystringUsed only where the store has saved no value of its own.
schemequerystringUsed only where the store has saved no value of its own.
logoUrlquerystringUsed only where the store has saved no value of its own.

GET /storefront/i18n

Languages this store serves, plus the packs installed on the platform

ScopeSign-inSince
catalog:readKey only2026-09

POST /storefront/inquiries

Submit a public contact enquiry

ScopeSign-inSince
inquiries:writeKey only2026-09
NameInTypeRequiredNotes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
_honeybodystringA hidden trap field; leave it empty. A filled one is dropped as spam.
emailbodystring emailYes
messagebodystringYes
namebodystringYes
phonebodystring
sourcebodycontact / inquiry
subjectbodystringYes

GET /storefront/modules

List the platform's module library

ScopeSign-inSince
catalog:readKey only2026-10
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /storefront/modules/{name}

Read one module with its guide

ScopeSign-inSince
catalog:readKey only2026-10
NameInTypeRequiredNotes
namepathstringYes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.

GET /storefront/page/{key}

Read a published Customizer page by key

ScopeSign-inSince
content:readKey only2026-09
NameInTypeRequiredNotes
keypathstringYes

GET /storefront/payment-gateways

List enabled payment methods; credentials are never exposed

ScopeSign-inSince
payment-methods:readKey only2026-09
NameInTypeRequiredNotes
localequerystringLanguage code, e.g. en or de. Names and messages come back in it.
currencyquerystringISO 4217 code the buyer is charged in; an unserved one falls back to the store's main currency.
baseTotalCentsquerystringThe basket total in the store's main currency, for method rules.
countryquerystringThe buyer's country code, for method rules.
familiesquerystringComma list of the basket's product families, for method rules.

GET /storefront/products

List active products, optionally by category

ScopeSign-inSince
catalog:readKey only2026-09
NameInTypeRequiredNotes
categoryquerystringCategory slug.

GET /storefront/redirects

List the storefront's configured URL redirects

ScopeSign-inSince
content:readKey only2026-09

GET /storefront/settings

Read public checkout settings including VAT percent

ScopeSign-inSince
catalog:readKey only2026-09

GET /storefront/theme

Read the active theme, menus and footer for rendering

ScopeSign-inSince
content:readKey only2026-09

tickets

GET /tickets

List the customer's tickets

ScopeSign-inSince
tickets:readCustomer sign-in2026-09
NameInTypeRequiredNotes
statusquerystring
departmentquerystringDepartment id.

POST /tickets

Open a support ticket

ScopeSign-inSince
tickets:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
Idempotency-KeyheaderstringA unique value per intended request (1 to 200 of A-Z a-z 0-9 - _). A retry with the same key and body returns the first answer instead of repeating the work.
bodybodystringYes
departmentIdbodystringYes
paidbodybooleanAn URGENT ticket needs a paid ticket credit: without paid, it is refused.
prioritybodyLOW / NORMAL / HIGH / URGENT
serviceIdbodystring
subjectbodystringYes

GET /tickets/{id}

Read one ticket with its replies

ScopeSign-inSince
tickets:readCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /tickets/{id}/attachments

Attach files to a ticket or reply

ScopeSign-inSince
tickets:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /tickets/{id}/close

Close a ticket

ScopeSign-inSince
tickets:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes

POST /tickets/{id}/replies

Reply to a ticket

ScopeSign-inSince
tickets:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
idpathstringYes
bodybodystringYes
internalbodybooleanStaff notes only; a customer who sets it is refused.

GET /tickets/departments

List the departments a ticket can be opened against

ScopeSign-inSince
tickets:readCustomer sign-in2026-09

users

GET /users/me

Read the signed-in customer's profile

ScopeSign-inSince
customers:readCustomer sign-in2026-09

PATCH /users/me

Update the signed-in customer's profile

ScopeSign-inSince
customers:writeCustomer sign-in2026-09
NameInTypeRequiredNotes
addressbodyobject
countryCodebodystring
currencybodystringThe currency the customer is billed in; must be one the store lists.
customerTypebodyINDIVIDUAL / BUSINESS
emailbodystring email
localebodystring
namebodystring
phonebodystring
vatIdbodystring

GET /users/me/export

Export everything this account holds

ScopeSign-inSince
customers:readCustomer sign-in2026-09