Conventions
The Printcart REST API v1 returns a small set of consistent response envelopes, paginates every list with an opaque cursor instead of page numbers, and rate-limits at 1000 requests per minute per credential. This page documents those conventions plus how errors, idempotency and versioning work, so a client written against one endpoint behaves correctly against all of them.
Updated . Maintained by the Printcart team.
Success envelopes
A single resource:
{ "data": { "id": "...", "...": "..." }, "message": "Retrieved successfully" }A collection, which is cursor-paginated:
{
"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.
| Parameter | Default | Notes |
|---|---|---|
limit | 20 | Maximum 100. A larger value is rejected, never clamped |
cursor | none | Opaque value from meta.next_cursor |
sort | desc | asc or desc |
sortBy | id | Use only documented column names |
s or search | none | Free-text search on endpoints that support it |
GET https://api.printcart.com/v1/products?limit=10A 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:
| Shape | Typical source |
|---|---|
{"error":{"message":"..."}} | store-key authentication, domain errors |
{"message":"..."} | public-token and JWT authentication, not found |
{"error":{"<field>":["..."]},"message":"Invalid input"} | request validation |
| Status | Meaning |
|---|---|
| 400 | Validation failed (not 422), a plan limit was reached, or a domain rule such as a duplicate product |
| 401 | Missing or invalid credential |
| 402 | Plan capability not available, media storage cap reached, or an empty token wallet |
| 403 | Authenticated but not permitted |
| 404 | Not found. Another store's resource also answers 404, so existence is never leaked |
| 409 | Conflict, for example a duplicate side-template import |
| 413 | limit above 100 on /design-templates and /side-templates |
| 422 | A few endpoints that validate inline rather than through a request class |
| 429 | Rate limited |
| 5xx | Internal 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-Keyis not implemented. Retrying aPOSTcreates a second resource, so make your writes idempotent on your side.PUT /side-templates/{sideTemplate}/productsaccepts anidempotency_keyfield in the JSON body to de-duplicate asset contributions.X-Request-Idis not implemented. To raise a support request, note the time, your storesidand the endpoint.Accept-Versionis not implemented.v1is the only version, and new response fields are additive.
Questions & answers
How do I know if there are more results in a list?
- Lists are cursor-paginated, not page-numbered. Follow links.next from the response until it is null; links.first, links.last and a total count are not provided.
What does a 402 response mean?
- The store's plan does not include that capability, its media storage cap is reached, or its token wallet is empty. Branch on the HTTP status, not the body shape, since several error-body shapes are in use.
Is there an Idempotency-Key header?
- Not yet. Retrying a POST creates a second resource, so make writes idempotent on your side; one exception is PUT /side-templates/{sideTemplate}/products, which accepts its own idempotency_key field.
Can you work with a merchant in my country?
- Yes. Printcart services are delivered remotely, so we work with merchants in any country. Netbase JSC, the company behind Printcart, has worked with clients in 80+ countries.
Do you support my industry?
- Printcart services are not tied to one industry. Full stores, custom development, integration and support are delivered remotely; tell us what you sell and we will say plainly whether Printcart fits.
How do I pay securely?
- You sign the contract with and pay the company, Netbase JSC. App plans are billed through Shopify or Wix for stores on those platforms, and through PayPal for WooCommerce and direct stores. Full-store orders on solution.printcart.com check out through PayPal. Service work is quoted in writing in your Helpdesk ticket before any payment.
Who do I talk to?
- Talk to our CEO, Huy Nguyen (David), on WhatsApp at https://wa.me/84937869689 or by email at support@printcart.com.