Authentication

Auth model, credentials, and tokens for the Esperto Assessments API.

Auth types

When creating the API client you pick an Auth-type. Each has different access:

Auth-type Access Use for
OAuth2 (recommended) Full Required for any real interaction (writes).
API Key Read-only (limited) Read access only
Basic Read-only (limited) Read access only

⚠️ API Key and Basic auth can only READ. For actual interaction (create/update/delete) you MUST use OAuth2.

Auth model

The API runs an OAuth 2.0 server (PHP League OAuth2) and supports two grant types, depending on whether a human is in the loop:

Grant Who uses it How the token is obtained
Authorization Code Interactive setups (e.g. Postman, a browser-based app) User consents in the browser at /auth/authorize; token delivered to the callback URL.
Client Credentials Headless / server-to-server clients (e.g. the MCP bridge) Direct POST to /auth/access_token — no browser, no callback. This is the one that works unattended.

There is always a 2-step call: first obtain a token, then use it to query.

Setting Value
Authorization URL api/v1/auth/authorize
Access token URL /api/v1/auth/access_token
Token format Bearer token, passed in the Authorization header

Flow (Authorization Code — interactive)

  1. The client sends its client_id and client_secret in the POST body to the authorization endpoint (api/v1/auth/authorize).
  2. The authorization endpoint sends a token to the pre-registered return (callback) URL.
  3. That token is then used as a Bearer token in the Authorization header to query the API.

This is the setup as configured/tested in Postman.

Flow (Client Credentials — headless / server-to-server) ✅

For an unattended client there is no browser step and no callback. Request the token directly:

POST https://scalinguptoolkit.com/api/v1/auth/access_token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
client_id=<client_id>
client_secret=<client_secret>
scope=members groups        # only the scope(s) you intend to use

Key details (each one is a real failure point — see FAQ & Troubleshooting):

  • Endpoint is /auth/access_token, NOT /auth/authorize (the latter is the interactive browser page — POSTing to it returns 405 + HTML).
  • Body must be application/x-www-form-urlencoded, not JSON (the PHP backend reads $_POST).
  • grant_type=client_credentials is required; omitting it returns 400 unsupported_grant_type.

The response is JSend-wrapped; the bearer token sits inside data. Use it as Authorization: Bearer <token> on every API call. This is exactly how the MCP bridge authenticates.

Callback (return) URL

  • The callback URL — where the access token is sent — is configured by Esperto when creating the API credentials (field "url the access token information will be sent to").
  • The customer must give Esperto their return URL, and Esperto sets it on the client. It is not something the customer can set themselves.

Scope matching ⚠️

The scope requested at authorization time must match the scopes enabled on the API client. Include in the token request every scope family you intend to call (e.g. members, groups, and/or campaigns). An endpoint only works if its scope is present in the token.

⚠️ With client_credentials, the token carries only the scopes you ask for — not everything the client is allowed. Enabling a scope on the client is necessary but not sufficient: if your token request's scope omits it, those endpoints return 403 even though the client could use them. (Real example: a token requested with members groups got 403 on /campaigns until campaigns was added to the request.) See FAQ & Troubleshooting.

Access is always limited to the end-points that have been opened for the client.

Credentials

  • The customer is issued a client ID and client secret (shared by Esperto out-of-band).
  • 🔑 The client secret is shown only once, at creation. It cannot be retrieved afterwards — like a password. If lost, a new one must be generated.
  • ⚠️ Changing essential details of the API client regenerates a new client-secret. This is for security: the scopes are coupled to the credentials, so changing what the client can access invalidates the old secret and forces re-issue.

Tokens / sessions

  • Access token is passed as Authorization: Bearer <token>.
  • Lifetime: max 1 hour. Refresh-token requests are blocked. Recommended pattern: request a fresh token for every block of queries you intend to run.
  • ⚠️ Server-side, the host must forward the Authorization header to the API code or every token reads as empty. See FAQ & Troubleshooting.

Response format (JSend) ⚠️

  • All endpoints return HTTP 200 on a successfully connected request — even when the result set is empty or the request was rejected during parameter validation (e.g. a duplicate email). Never infer success from the HTTP status code.
  • With a few exceptions, responses use the JSend envelope { status: ..., data: ... }:
    • status: OK → request succeeded; data contains the requested data.
    • status: ERRORdata contains the error message.

Was this helpful? Get in touch if something is missing or unclear.