Skip to main content

API Conventions

Every SalesOS API follows the rules on this page. Read it once and the individual endpoint pages become short: they only tell you what is specific to them.
This page is the contract we hold ourselves to. Where an endpoint departs from it, that endpoint’s page says so explicitly — a silent exception is a bug, and we want to hear about it.

Base URLs

https://api.play2sell.com
Every endpoint lives under /functions/v1/<name>. There is no /v1/... REST surface — if you find a page describing one, it is out of date; please tell us.

Authentication

Two schemes, and they are disjoint — an endpoint accepts one or the other, never both for the same caller.

Partner (server-to-server)

The signature is an HMAC over {api_key_id}.{timestamp}.{raw_body}. See Authentication for the signedRequest helper.
  • Timestamps older than 300 seconds are rejected (replay protection)
  • Sign the raw body bytes, before any re-serialization
  • Each key is scoped to a single company; a document from another company answers found: false

Self mode (federated session)

Used when your app already has the end user signed in. The response covers only that user.
Sending a collaborators list together with a user token is rejected with SELF_MODE_NO_COLLABORATORS. Batch lookups require a partner API key — otherwise any signed-in user could read other people’s data by CPF.

Error shapes

Requests fail in two different layers, and they answer differently. Handle both — this is the single most common integration bug we see.

Authentication layer

Before your request reaches the endpoint’s own logic:
error is a string. Common codes: header_missing, header_malformed, invalid_key, signature_mismatch, timestamp_expired.

Endpoint layer

Once authentication passed:
error is an object. details points at the offending item by index when the request carried a list.
error is a string in one case and an object in the other. Code that assumes error.code will read undefined on every authentication failure — and code that assumes error is a string will print [object Object] on validation failures. Branch on the type:
We know this is not ideal. It is documented rather than hidden because the two layers ship separately, and a partner discovering it in production is worse than a partner reading it here.

Payments

The Payments API answers with RFC 7807 problem+json instead:
It is a separate service with its own conventions. instance is the request id — quote it when opening a ticket.

Status codes

Absence is not an error. No campaign running, nobody checked in, no missions today — all answer 200 with an empty structure. An endpoint that returned 404 for “nothing today” would make your screen say something is broken when the day simply started.

Batching and identity

Partner endpoints take a collaborators array and answer in the same order, one entry per request item.

Identity resolution

Precedence is user_id > cpf > email. When more than one is sent, the first present wins — the others are ignored, not used as a fallback.
  • CPF is accepted in any format; digits are normalized. It must contain exactly 11 digits.
  • Unknown person answers "found": false. That is not an error, and it keeps the array aligned with your request.
  • "ambiguous": true appears when more than one person matched — treat it as unresolved.

Limits

Batch limits are per action and stated on each page, because they follow the payload each action produces. As a rule: a status action allows 500, an action that expands a list per person allows 100 or fewer.
Shared data — campaign metadata, the tenant’s shifts — comes back once at the top of the response, not repeated per person. On a 500-person batch that is the difference between a lean response and a multi-megabyte one.

Configuration belongs to the company, never to your code

This is the rule that most often breaks integrations months after they ship. Names, labels, counts, thresholds and currencies are configured per company and returned in the response. They are not constants.
Render what the response gives you. A screen that hardcodes a label works until the day the customer renames it — and then it lies, quietly, with no error anywhere.

Timezones and dates

Every “today” is computed in the company’s timezone, never UTC. The response states which one it used:
Timestamps are ISO 8601 in UTC. Convert for display using the timezone from the same response — not the device’s.

Rate limits

Configurable per key, defaulting to 1000 requests/hour. On 429, the response carries retry_after in seconds. Back off; do not spin.

Versioning

Contracts are extended, not broken. We may add fields to a response at any time, so parse permissively and ignore what you do not know. New behavior that changes an existing payload ships opt-in, behind a request flag (for example include_day). Omitting the flag keeps the response you already handle.

Security

  • Partner APIs are read-only unless the page says otherwise — they never redeem, never change balances, never accept terms
  • Requests are HMAC-signed and logged for audit; documents and emails are never written to logs
  • Never call these APIs from client-side code: the key would be exposed

Next steps

Authentication

Create keys and sign requests

Check-in API

Duty presence and the day’s shift track

Missions API

Progress, points earned and points available

Campaigns API

Reward catalog and spendable balance