Quickstart#

From nothing to a first successful call.

1. Get credentials#

Integration clients are registered by us, not self-service. Email support@shiftavo.com with:

  • which company the integration is for,

  • what it needs to do — we grant only the scopes that covers (see Scopes).

You get back three things:

client_id

e.g. cust-acme-integration

client_secret

shown once, stored hashed on our side — unrecoverable, so save it immediately

scopes

e.g. shifts.read timesheets.read

One set of credentials belongs to exactly one company and cannot reach another, even if you belong to several.

2. Get an access token#

POST to the identity provider’s token endpoint. Credentials go in the form body:

curl -X POST https://auth.shiftavo.com/identity/o/api/token \
  -d grant_type=client_credentials \
  -d client_id=cust-acme-integration \
  -d client_secret=$CLIENT_SECRET \
  -d scope=shifts.read
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 1800,
  "scope": "shifts.read"
}

Important

Cache the token and reuse it until shortly before it expires. This is a requirement, not an optimization — do not call the token endpoint once per request.

3. Find out which host to call#

The access token is a JWT carrying an api_base claim that names your company’s shard:

{ "api_base": "https://app.shiftavo.com", "tenant_id": "0194d1c0-…", "…": "…" }

Read the host from the token rather than hardcoding it — that is what lets us add or move shards without you changing anything. Decode the payload as a routing hint only; you do not need to verify your own token.

4. Make a call#

curl "$API_BASE/api/public/v1/locations/?page_size=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "object": "list",
  "data": [
    { "object": "location", "id": "0194d1c0-…", "name": "Bar — Hamptons" }
  ],
  "has_more": false,
  "next": null
}

If your own locations come back, the credentials, the scopes, and the company binding are all correct.

The same thing in Python#

import requests

AUTH = "https://auth.shiftavo.com"

token = requests.post(
    f"{AUTH}/identity/o/api/token",
    data={
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "scope": "shifts.read",
    },
    timeout=30,
).json()

# `api_base` is a routing hint from the token — decode, don't verify.
import base64, json
payload = token["access_token"].split(".")[1]
api_base = json.loads(base64.urlsafe_b64decode(payload + "=" * (-len(payload) % 4)))["api_base"]

shifts = requests.get(
    f"{api_base}/api/public/v1/shifts/",
    headers={"Authorization": f"Bearer {token['access_token']}"},
    timeout=30,
).json()

Next#

  • Authentication — the token flow in full, including expiry and 401 handling.

  • Conventions — pagination, errors, idempotency. Read this before writing a client.

  • API Reference — every endpoint.