Skip to content

Crossmart for developers

Crossmart keeps one catalog and one stock ledger, and publishes them to every store a seller connects (Shopify, eBay, Google Merchant and more). Everything the dashboard does goes through a public REST API, so your scripts, integrations and AI agents can do it too.

Base URL https://api.crossmart.io/api/v1 (self-hosted: http://localhost:3000/api/v1)
API reference On this site, generated from the OpenAPI spec
Interactive docs api.crossmart.io/api/docs
OpenAPI spec api.crossmart.io/api/docs-json
Format JSON in and out. Money is a decimal string ("19.90"), times are ISO 8601 in UTC

The API reference lists every endpoint with its fields. The interactive docs let you try them with your key (Authorize → api_key). Generate a client in any language from the OpenAPI spec, or use the TypeScript or Python SDK.

The version is in the path: /api/v1. Within a version, changes are additive only: new endpoints, new optional fields, new response fields and new enum values. Write clients that ignore fields they don’t know and tolerate new enum values. Removing or renaming anything goes through a deprecation period and, if it can’t be avoided, a new version.

An organization is the account that holds the plan and billing. It has one or more workspaces: a workspace is one business, with its own catalog, stock, stores, orders and members. Almost every endpoint is scoped to a workspace:

/api/v1/workspaces/{workspaceId}/products
/api/v1/workspaces/{workspaceId}/orders

A workspace you can’t access answers 404, the same as one that doesn’t exist, so ids don’t leak. Plan limits (active listings, connected stores, API keys…) count across the organization’s workspaces.

An API key belongs to one workspace and has a role. Create one in the dashboard under Settings → API keys (or with POST /api/v1/workspaces/{workspaceId}/api-keys). The full key is shown once; Crossmart stores only a hash. Send it in the x-api-key header:

Terminal window
curl https://api.crossmart.io/api/v1/api-key -H "x-api-key: $CROSSMART_API_KEY"

GET /api/v1/api-key answers with the key’s workspaceId, role and permissions, so a program holding only the key can find its workspace.

Role Typical use
VIEWER Read-only reporting and dashboards
EDITOR Catalog and stock sync, publishing to stores, generating AI content for review
APPROVER Reviewing AI suggestions
ADMIN Store pricing and sync rules, members and keys
CUSTOM A role the workspace defined, with exactly the permissions you pick

A key can’t be an Owner and never has more access than the person who created it. Each endpoint requires one permission; a key without it gets 403 Requires the <permission> permission. The permissions of each role are in Roles and permissions. Give each integration its own key with the smallest role that works, so you can revoke it alone.

AI agents such as Claude and ChatGPT will be able to connect through OAuth instead of a pasted key: the seller signs in, picks a workspace and approves the access. The agent then sends an access token as Authorization: Bearer <token>, limited to the scopes the seller approved and to their own role. The SDKs already accept an access token.

The dashboard signs people in with a session cookie (Better Auth, under /api/auth). Use API keys for programs; session endpoints such as /api/v1/me are meant for the web app.

Errors use the HTTP status and a JSON body:

{ "statusCode": 404, "error": "Not Found", "message": "Product not found" }
Status Meaning
400 Invalid input. message is a list with one entry per problem (see below).
401 The key or token is missing, wrong, revoked or expired.
402 The plan doesn’t allow it. The body adds a machine-readable code, the feature, plan, limit and usage.
403 The role lacks the permission.
404 Not found, or not in this workspace.
409 Conflicts with existing data, such as a duplicate SKU or handle.
429 Too many requests (see rate limits).
5xx A server problem. Safe requests (GET, PUT, DELETE) can be retried with backoff.

A 400 lists every problem:

{
"statusCode": 400,
"error": "Bad Request",
"message": ["title should not be empty", "variants must contain at least 1 elements"]
}

A 402 has code set to plan_limit_reached or plan_feature_not_included, plus the feature, the plan, its limit and current usage, so you can tell the user what to upgrade.

List endpoints return one page at a time:

{ "items": [ … ], "nextCursor": "6f1c…", "total": 137 }
  • limit: page size, 1 to 100 (the default is 25 on most lists).
  • cursor: pass the previous page’s nextCursor to read the next page. nextCursor is null on the last page.
  • page: a 1-based page number, for tables that jump between pages. It wins over cursor.
  • total: how many items match the filters in all.

Unlike page numbers, cursors do not skip or repeat items when rows are added while you read. The SDKs walk the pages for you: listAll() in TypeScript, iter() in Python.

Most /api/v1 endpoints have no rate limit today. Two are limited, per workspace and per caller (a person or an API key), because each call spends AI credits:

Endpoint Limit
POST /api/v1/workspaces/{workspaceId}/ai/content-generations 10 requests a minute
POST /api/v1/workspaces/{workspaceId}/ai/content-drafts 30 requests a minute

Sign-in and sign-up under /api/auth are limited too. Past a limit the API answers 429 Too Many Requests with a Retry-After header. Limits per key may be added to other endpoints later, answered the same way. Handle 429 now (the SDKs do), and batch your work: list with large pages, and use the bulk endpoints such as POST …/channel-connections/{connectionId}/products/add.

Talking to marketplaces takes time and can fail, so changes that reach a store run in the background. The API records the change and answers at once; a worker then publishes it. Publishing a product creates a listing with status PENDING, which becomes ACTIVE when the store accepts it or ERROR (with the store’s message in lastError) when it refuses. The quickstart shows how to follow it.