OAuth for software vendors

For software that works for many agencies: the agency grants access and you never touch its API key.

If you are a software vendor, use OAuth 2.0 instead of asking every agency for its API key. The agency approves your software on a consent page and you receive an access token limited to what that agency approved. The agency can withdraw access at any moment.

1. Register your app

On the Developer page, Apps tab, add a new app: a name, redirect addresses (https, or http://localhost locally) and the client type. Server apps are confidential clients and get a client_secret (shown once); mobile and browser apps are public clients. Both must use PKCE.

js
import crypto from 'node:crypto';

const verifier = crypto.randomBytes(32).toString('base64url');          // keep it with the user's session
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');

const url = new URL('/en/app/oauth/authorize');
url.search = new URLSearchParams({
  client_id: process.env.BP_CLIENT_ID,
  redirect_uri: 'https://app.example.com/callback',
  scope: 'properties:read calendar:read rates:read reservations:write events:read',
  state: crypto.randomBytes(16).toString('hex'),                        // check it again on return
  code_challenge: challenge,
  code_challenge_method: 'S256',
  mode: 'live',                                                         // or 'test' for the sandbox
}).toString();
// redirect the browser to url.toString()

If the agency is not signed in it signs in first. If it approves, it returns to your redirect_uri with code and state; if it declines, with error=access_denied.

3. Exchange the code for tokens

bash
curl -s -X POST https://api.cesisync.com/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=$BP_CLIENT_ID -d client_secret=$BP_CLIENT_SECRET \
  -d code=$CODE -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=$VERIFIER
json
{
  "access_token": "bp_oat_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "bp_ort_…",
  "scope": "properties:read calendar:read rates:read reservations:write events:read"
}

The code is valid for 10 minutes and works once; a second attempt withdraws every token made from that code. Public clients send no client_secret; PKCE is enough.

4. Call the API

bash
curl -s https://api.cesisync.com/v1/me -H "Authorization: Bearer bp_oat_…"

The access token works on /v1 like an API key but only with the scopes and environment (live or sandbox) the agency approved. The same rate limits and error codes apply; when it expires you get 401 api_key_expired.

5. Refresh

bash
curl -s -X POST https://api.cesisync.com/oauth/token \
  -d grant_type=refresh_token -d client_id=$BP_CLIENT_ID -d client_secret=$BP_CLIENT_SECRET \
  -d refresh_token=bp_ort_…

Each refresh gives a new access and refresh token and retires the old ones (rotation). A refresh token lasts 60 days. To revoke tokens call POST /oauth/revoke (token, client_id, client_secret).

The OAuth endpoints answer errors in RFC 6749 form: {"error":"invalid_grant","error_description":"…"}. Every other API error is problem+json.