Conventions

Response envelopes, cursor pagination, errors, rate limits and versioning.

Success envelopes

A single resource:

json
{ "data": { "id": "...", "...": "..." }, "message": "Retrieved successfully" }

A collection, which is cursor-paginated:

json
{
  "data": [],
  "links": { "first": null, "last": null, "prev": null, "next": "https://...?cursor=..." },
  "meta": { "path": "https://...", "per_page": 20, "next_cursor": "...", "prev_cursor": null },
  "message": "Retrieved successfully"
}

message is a human-readable string, not a contract. Do not branch on it.

Pagination

Lists are cursor-based, not page-number based. links.first and links.last are always null and there is no total, so you cannot render "page 3 of 12" from the response. Walk forward by following links.next until it is null.

ParameterDefaultNotes
limit20Maximum 100. A larger value is rejected, never clamped
cursornoneOpaque value from meta.next_cursor
sortdescasc or desc
sortByidUse only documented column names
s or searchnoneFree-text search on endpoints that support it
http
GET https://api.printcart.com/v1/products?limit=10

A limit above 100 returns 400 on almost every list endpoint, with the standard validation body. GET /design-templates and GET /side-templates answer 413 instead. The simplest way to handle both is never to send a limit above 100.

Errors

Branch on the HTTP status, not on the body shape and not on the message. Several body shapes are in use, depending on the layer that rejected the request:

ShapeTypical source
{"error":{"message":"..."}}store-key authentication, domain errors
{"message":"..."}public-token and JWT authentication, not found
{"error":{"<field>":["..."]},"message":"Invalid input"}request validation
StatusMeaning
400Validation failed (not 422), a plan limit was reached, or a domain rule such as a duplicate product
401Missing or invalid credential
402Plan capability not available, media storage cap reached, or an empty token wallet
403Authenticated but not permitted
404Not found. Another store's resource also answers 404, so existence is never leaked
409Conflict, for example a duplicate side-template import
413limit above 100 on /design-templates and /side-templates
422A few endpoints that validate inline rather than through a request class
429Rate limited
5xxInternal error

Most errors carry no machine-readable code. storage_quota_exceeded and capability_not_available (both under error.code) and insufficient_tokens (top-level code) are the exceptions. Write clients that tolerate both a string error and an object error.

Rate limits

The general limit is 1000 requests per minute, keyed by the authenticated principal and falling back to the client IP. It is not tiered by plan and it is not per endpoint. X-RateLimit-Limit and X-RateLimit-Remaining are on every response; Retry-After and X-RateLimit-Reset appear on 429 responses.

The AI Services and Studio AI endpoints have their own, lower limit. If your workload is bursty, such as a bulk import or a nightly reconciliation, pace it yourself.

Idempotency, request IDs and versions

  • Idempotency-Key is not implemented. Retrying a POST creates a second resource, so make your writes idempotent on your side. PUT /side-templates/{sideTemplate}/products accepts an idempotency_key field in the JSON body to de-duplicate asset contributions.
  • X-Request-Id is not implemented. To raise a support request, note the time, your store sid and the endpoint.
  • Accept-Version is not implemented. v1 is the only version, and new response fields are additive.