Getting Started
Authentication & signing
The Partner API uses two layers of authentication: an HMAC signature that proves the request came from your app, and a workspace access token that scopes the request to one customer's data.
Both layers are required on data endpoints
Data endpoints need both a valid HMAC signature (your app) and a Bearer access token (the workspace). Only the token-exchange routes are signed without a Bearer token.Layer 1 — HMAC request signing
Every request carries three headers derived from your clientSecret. The signature is an HMAC-SHA256 of a base string built from the request:
METHOD:path:timestamp:bodyHash
# Example for: POST /partners/customers
POST:/customers:1717689600:9b74c9897bac770ffc029102a200c5deThe four parts, joined by colons, are:
METHOD— the uppercase HTTP method.path— the path relative to the/partnersmount. For/partners/customersyou sign/customers.timestamp— Unix time in seconds; send the same value inx-timestamp. Must be within ±300 seconds of server time (five-minute clock skew window).bodyHash— SHA-256 hex of the exact JSON body for POST/PUT/PATCH, or an empty string otherwise.
Sign the path without /partners
The most common signing mistake is including the/partners prefix in the base string. The server signs req.path, which is relative to the mount.import crypto from 'node:crypto';
function signRequest({ method, path, timestamp, body, clientSecret }) {
// path is relative to the /partners mount, e.g. "/customers"
const bodyHash = ['POST', 'PUT', 'PATCH'].includes(method)
? crypto.createHash('sha256').update(body ?? '').digest('hex')
: '';
const baseString = `${method}:${path}:${timestamp}:${bodyHash}`;
return crypto.createHmac('sha256', clientSecret).update(baseString).digest('hex');
}Attach the result as headers:
x-client-id: <your clientId>
x-timestamp: <unix seconds, same value used in the signature>
x-signature: <hex HMAC-SHA256 of the base string>
Authorization: Bearer <workspace access token> # data endpoints only
Content-Type: application/json # when sending a bodyLayer 2 — Workspace access tokens (OAuth)
To act on a workspace you first send the user through the OAuth consent flow, then exchange the returned code for an access token. The token exchange is signed with HMAC but does not need a Bearer token.
Use the arrows or dots below to step through each OAuth phase. Details and code samples update as you browse.
Create OAuth credentials
Register your integration in COUNT Partners and store clientId and clientSecret on your server.
- Create an OAuth app in the COUNT Partners dashboard.
- Keep clientSecret server-side only; never ship it to browsers or mobile apps.
See OAuth consent experience for what your users see on the consent screen and how to handle the redirect.
POST https://api.getcount.com/partners/grant-access-token
{
"code": "<authorization code from the redirect>",
"grantType": "authorization_code"
}The response contains the tokens and the workspace they are scoped to:
{
"accessToken": "eyJhbGciOi...",
"refreshToken": "eyJhbGciOi...",
"accessTokenExpiresAt": "2026-06-06T12:00:00.000Z",
"refreshTokenExpiresAt": "2026-07-06T12:00:00.000Z",
"workspaceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"workspaceName": "Acme Books"
}Access tokens expire. When they do, call POST /partners/refresh-user-access-token with your refresh token to get a new pair.
Putting it together
Sign the base string, set the three HMAC headers plus the Bearer token, and send the request. Every code sample in this reference does exactly this — the starter templates wrap it into a single helper so you never compute a signature by hand.
