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)
- The client sends its
client_idandclient_secretin the POST body to the authorization endpoint (api/v1/auth/authorize). - The authorization endpoint sends a token to the pre-registered return (callback) URL.
- That token is then used as a Bearer token in the
Authorizationheader 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_credentialsis required; omitting it returns400 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
Authorizationheader 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;datacontains the requested data.status: ERROR→datacontains the error message.