Conventions
Base URL and versions
Section titled “Base URL and versions”All endpoints live under https://api.tradeops.cc/v1. The same API also answers at https://tradeops.cc/api/v1, which is what links in responses use. Changes within v1 only add things: new endpoints, new optional parameters and new response fields. Clients should ignore fields they don’t know. Anything that would break a client goes into a new version, and the changelog lists every change.
Send Accept: application/json on every request, so errors come back as JSON instead of a redirect.
Authentication
Section titled “Authentication”Send a bearer token on every call except sign-in:
Authorization: Bearer 1|Kq3…There are two kinds of token:
- Field-app tokens come from signing in. There’s one per device, they work on every plan and they don’t count towards API usage.
- Integration tokens are for customers’ own systems. They need a plan with API access, and every call counts towards the organisation’s API usage.
Tokens don’t expire. Revoke one with DELETE /auth/token.
Organisations
Section titled “Organisations”Most routes start with an organisation’s slug: /v1/{team}/customers. GET /me lists the organisations a member belongs to. Every call is checked against the member’s role in that organisation, so a rep and a manager can see different results from the same endpoint.
Dates follow the organisation’s time zone (timezone in GET /me). Timestamps are ISO 8601 in UTC.
Formats
Section titled “Formats”Resources follow JSON:API and come back as application/vnd.api+json:
{ "data": { "id": "01JC8…", "type": "customers", "attributes": { "name": "Half Way Tree Pharmacy", "city": "Kingston" }, "relationships": { "locations": { "data": [] } }, "links": { "self": "https://api.tradeops.cc/v1/island-grocers/customers/01JC8…" } }}- Records are identified by their ULID (
id). includeembeds related records inincluded:?include=auditType.fields,customer.- Sparse fieldsets trim attributes:
?fields[audit_types]=name,status.
Sign-in and the offline sync return plain JSON.
Pagination
Section titled “Pagination”GET /{team}/assignments is paginated. Use page and per_page (up to 100, default 50) and follow links.next until it’s null. Other lists return every record.
Errors
Section titled “Errors”| Status | Meaning |
|---|---|
401 |
The token is missing, revoked or wrong. |
403 |
The member’s role doesn’t allow it, or the organisation’s plan doesn’t include it. |
404 |
The record doesn’t exist in this organisation. |
422 |
The input is invalid. errors lists messages for each field. |
429 |
Too many requests. Wait for Retry-After seconds. |
Validation errors look like this:
{ "message": "The name field is required.", "errors": { "name": ["The name field is required."] }}When the plan is the reason for a 403, the body says which feature is missing:
{ "message": "API access isn't included in your Growth plan. Contact TradeOps to enable it.", "entitlement": "api_access"}Rate limits
Section titled “Rate limits”| Endpoint | Limit |
|---|---|
| Everything signed in | 60 requests a minute per member |
POST /auth/token |
5 attempts a minute per email and IP address |
POST /auth/phone/code |
3 codes in 15 minutes per number, 10 an hour per IP address |
POST /auth/phone/token |
20 attempts in 15 minutes per IP address |
The texted-code limits return 422 with a message rather than 429, so the app can show it next to the field.