Skip to main content
Conventions that hold across the whole API. Individual endpoints document what is specific to them; everything here is assumed.

Requests

Unknown fields are rejected, not ignored. Sending a property the endpoint does not define returns 400 with "property <name> should not exist". This turns a typo into an immediate error instead of a silently dropped value — but it also means you cannot round-trip a response object straight back into a PATCH. Send only the fields you intend to change.

List responses

Every list endpoint is unpaginated by default and paginated on request. Send neither limit nor cursor and you get the complete JSON array these endpoints have always returned:
Send either one and the response becomes an envelope:

Parameters

Walking a list

Stop when hasMore is false. Do not stop on a short page — a page can come back shorter than limit and still have more behind it.
Why cursors and not page numbers. These lists are ordered newest-first, so a record created while you are paging lands on page 1 and shifts every subsequent offset by one — an offset client silently re-reads or skips records. A cursor anchors to a record rather than a position, so concurrent writes cannot corrupt a walk. The trade-off is no page numbers and no jumping to an arbitrary page.
Treat nextCursor as opaque and pass it back unmodified. A cursor whose record no longer exists ends the walk rather than restarting it.

Choosing a page size

Filtering server-side beats paging through everything. GET /reseller/catalog accepts category, minPrice, maxPrice, zipCode, and search; narrowing there is far cheaper than fetching every page and filtering locally. If you do not paginate, do not assume a bounded response size in your client — no fixed buffers, no hard-coded array-length expectations.

Filtering

Filters are query parameters, and they combine with AND. An unknown query parameter is rejected with 400, the same as an unknown body field.

Identifiers

Every resource id is a UUID v4 string.
Treat them as opaque: do not parse them, infer type or ordering from them, or assume any length other than 36 characters. Ids are stable for the life of the resource. The one exception is a shareable link’s slug, which appears in public URLs and is a short human-shareable token rather than a UUID.

Timestamps

All timestamps are ISO 8601 in UTC, with milliseconds and a trailing Z.
Send them in the same format. Date-only fields — a move-in date, an installation date — are calendar dates with no time component and no timezone; do not convert them through a local timezone or they can shift by a day.

Money

Monetary values are returned as strings, not numbers.
Parse money with a decimal library, not parseFloat. These are exact decimal values; binary floating point cannot represent them exactly, and rounding drift on prices and totals shows up in customer-visible numbers. Strings are used precisely so no precision is lost in transport.
All amounts are in USD, monthly, and before taxes and fees unless the offer states otherwise. Prices on an order are snapshotted at order time — an offer whose price changes later does not retroactively change existing orders.

Nulls and optional fields

A field that does not apply is null rather than absent, so response shapes stay stable. Two exceptions are genuinely conditional and absent when they do not apply:
  • electricity — present only on electricity offers
  • internet — present only on internet offers
Every offer carries a top-level serviceType discriminator ("electricity", "internet", …), so branch on that rather than probing for the grouped object.
Responses are additive over time. New fields can appear in any response without notice, so parse permissively and ignore what you do not recognize — a strict parser that rejects unknown response fields will break on a routine release. See Versioning.

Idempotency

There is no Idempotency-Key header today. POST /reseller/orders is not idempotent: two identical requests create two orders. If a write times out without a response, reconcile before retrying — GET /reseller/orders will tell you whether the first attempt landed. See Handling errors. Reads, PATCH, and DELETE are naturally idempotent and safe to retry.

Next steps

Errors

Every status code and a retry-safe client

Rate limits

What is limited, and what is not

Versioning

What we promise not to break

Authentication

Keys, headers, roles, and rotation