FAQ & Troubleshooting

Common questions and known issues.

Frequently asked questions

Why do the client-id and client-secret change when I edit the client? For security. The scopes are coupled to the credentials, so changing essential details (including what the client can access) regenerates the client-secret. The old secret stops working.

Where do I find the client secret again? You can't. The secret is shown only once, at creation — like a password, it cannot be retrieved afterwards. If lost, generate a new one.

Can I just use an API key / Basic auth? Only for reading. API Key and Basic auth are read-only. For any write interaction you must use OAuth2. See Authentication.

Who sets the callback (return) URL? Esperto does, when creating the credentials. The customer provides their return URL and Esperto configures it on the client.

My token works but a call is rejected — why? Likely a scope mismatch. The scope requested at authorization must match the scopes enabled on the client, and access is limited to the end-points opened for that client.

Is there a Users endpoint? Not yet. Users are not exposed via the API today, but it is planned.

My token request succeeds but every data call fails / returns nothing — why? First check the date. Before 2026-06-24 a server-side .htaccess bug stripped the Authorization header before it reached the API code, so bearer tokens could never be validated (see Known issues). This is now fixed. If you still see this after that date, verify (1) the scope you're calling is included in the token request, and (2) you're reading the status field in the JSend body rather than relying on the HTTP status code.

Why does a call return HTTP 200 but no data? All endpoints return HTTP 200 on a connected request — regardless of whether the result set is empty or the request was rejected during parameter validation (e.g. a duplicate email when adding members). Do not use the HTTP status code to determine success. With few exceptions, responses use the JSend format { status: ..., data: ... }. status: OKdata holds the result; status: ERRORdata holds the error message.

The MCP connector rejects my id — "Required: groupId". ⚠️ The MCP tools take camelCase identifiers — groupId, memberId — even though the API's own JSON data fields are lowercase (groupid, memberid). Passing groupid/memberid to a tool fails. Use camelCase for the tool arguments; expect lowercase in the returned data. See MCP Tools.

How long does a token last? Can I refresh it? Max 1 hour, and refresh-token requests are blocked. Request a fresh token for every block of queries you intend to run.

Known issues

✅ Resolved 2026-06-28 — 403 on /campaigns despite the scope being enabled

Symptom: GET /campaigns returned a hard HTTP 403 (a real 403, not a JSend status: ERROR), while /members and /groups kept working. The campaigns scope was enabled on the client and the secret had been updated and redeployed.

Root cause: With grant_type=client_credentials the access token only carries the scopes named in the token request's scope parameter. The request named members groups, so the token never contained campaigns — hence 403 on those endpoints only. Enabling a scope on the client is necessary but not sufficient; it must also be requested.

Fix: add the family to the requested scope string and restart so a fresh token is issued. GET /campaigns then returned JSend status: OK.

Takeaway: a 403 on one endpoint family while others work points at a missing scope in the token request, not a bad secret. Check the scope you send first.

✅ Resolved 2026-06-25 — adding a member to a group (singular/plural typo)

Symptom: POST /groups/{groupid}/members/{memberid} returned 404; trying to set the group via PATCH /members/{memberid} returned "Access denied". It looked like a permissions problem, but it was not — group writes work fine.

Root cause: the server had only registered the legacy singular route POST /groups/{groupid}/member/{memberid}. The plural path the connector (and docs) used, …/members/{memberid}, didn't exist → 404.

Fix (2026-06-25): both routes are now registered and both assign the member to the group:

POST /groups/{groupID}/member/{memberID}     # legacy (singular)
POST /groups/{groupID}/members/{memberID}    # plural — what the connector uses

💡 Best practice — run a dupecheck before creating

The API rejects duplicate email addresses (members) and duplicate group names/aliases (groups). Call the dupecheck endpoint first so you know up front whether a create will succeed:

  • POST /members/dupecheck — e.g. body { email } — before POST /members.
  • POST /groups/dupecheck — e.g. body { name, alias } — before POST /groups.

Connector tools: esperto_members_dupecheck and esperto_groups_dupecheck.

✅ Resolved 2026-06-24 — empty Authorization header (stale .htaccess)

Symptom: Bearer tokens could not be validated. Affected both the MCP bridge and at least one external customer integration simultaneously — confirming the fault was server-side, not in any client.

Root cause: A very old .htaccess on the API host was missing the rules that forward the Authorization header to PHP. Apache does not pass the Authorization header to PHP by default, so the token always read as empty. The missing rules:

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

Fix (2026-06-24): Added the three forwarding rules to the .htaccess. Bearer tokens are now validated correctly.

Takeaway: If bearer auth ever silently fails again after a server/host update, suspect the Authorization header is being stripped before PHP — check the .htaccess first.


Still stuck? Contact us.

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