Start here
No credential at all โ prove you can reach the server.
No authentication
no identityNothing to configure. Start here โ if this fails, the problem is the network or the server URL, not a credential.
- Auth mode
- None
200 with a server timestamp.
Credentials you paste
A fixed value you hold. Everyone using the server sends the same one.
API key โ custom header
no identityA static key in a header of the API's choosing. The most common enterprise mode after bearer.
- Auth mode
- I have an API key or token
- Header name
- X-API-Key
- Value
- sim-api-key-4d9e21
200, and authenticated_as is null โ a key identifies the application, not a person.
Send the right key under the wrong header name. The 401 names the header it wanted and the credential-looking headers that arrived, so you can tell a naming mistake from a wrong key.
API key โ query string
no identityThe same key, in the URL instead of a header. Older APIs, and it behaves differently.
- Auth mode
- None โ put the key in the tool URL
- URL
- https://mcp-simulators.studiox-ai.com/api/querykey?api_key=sim-api-key-4d9e21
200.
The key ends up in access logs, browser history and any Referer sent onward. That is the trade-off of this mode, not a defect โ worth seeing once before choosing it.
Static bearer token
shared identityA token you paste, sent as Authorization: Bearer on every call. No refresh, ever.
- Auth mode
- I have an API key or token โ Bearer
- Token
- any token minted by /oauth/token or /login/*
200 naming whoever the pasted token belongs to.
A pasted token expires silently. When it does, every tool on the server starts failing at once with no change on your side.
HTTP Basic
shared identityUsername and password on every request. It does carry an identity โ but a stored one, the same for everybody using the server.
- Auth mode
- Basic Auth
- Username / password
- alice / alice (or bob, carol)
200 naming alice.
Everyone who uses this server is alice, whoever they actually are. That is the distinction from per-user sign-in.
Custom headers
no identityTwo or more fixed headers โ a credential split across headers, or a key plus a tenant or account header.
- Auth mode
- Custom Headers
- X-Client-Id
- sim-client-42
- X-Client-Secret
- sim-secret-c7f1
200 listing both headers as verified.
Send only one. The error names which is missing rather than a bare 401.
Bearer plus a token-type header
carries a personThe token alone is not enough โ a second header says what kind of token it is. This is the Snowflake SQL API shape.
- Auth
- any bearer from /oauth/token or /login/*
- Extra header
- X-Sim-Token-Type: OAUTH
200 echoing the token type.
Omit the header. The 401 mentions the token, so the failure looks like a rejected credential โ which is exactly how this goes wrong in real templates that forget to bake the header in.
Credentials the server fetches
The server logs in for a token and refreshes it. Still one credential for everybody.
OAuth 2.0 โ client credentials
shared identityThe server fetches and refreshes its own token. One token, shared by everyone who uses the server.
- Auth mode
- The API logs me in for a token โ OAuth
- Token URL
- https://mcp-simulators.studiox-ai.com/oauth/token
- Client ID
- studiox-sim-client
- Client secret
- sim-secret-8f2a
- Grant
- client_credentials
whoami returns "Shared Service Account" with is_shared_service_account true, and orders returns all 9 rows.
Credentials work in either a Basic header or the request body. The response reports which arrived as sim_credentials_via โ use it to check what the client actually did rather than what the form says.
OAuth โ scope enforcement
shared identityRight client, right secret, wrong scope. The token is genuine and still refused.
- Token URL
- https://mcp-simulators.studiox-ai.com/oauth/token
- Scope
- orders.read
200 with the granted scopes listed.
Ask for a token with no scope, or a different one. You get 403 โ not 401 โ and the body says the credentials were fine and only the scope was wrong. Without that distinction this failure reads as a bad secret.
Log in for a token (not OAuth)
carries a personUsername and password to a login endpoint, a token back, bearer afterwards. Exensio-shaped, and common in enterprise estates.
- Auth mode
- The API logs me in for a token โ Log in for a token
- Login URL
- https://mcp-simulators.studiox-ai.com/login/nested
- Body fields
- username=alice, password=alice
- Token path
- data.token
whoami names alice; orders returns 3 EMEA rows.
Five response shapes at /login/{token, access_token, nested, session, xauth} carry the same token under different key names. Try nested even if your API is simpler โ a client that only reads top-level keys breaks there, and the symptom looks like bad credentials.
Custom header, no prefix
carries a personThe token goes in X-Auth-Token, raw. Two separate fields differ from their defaults at once.
- Login URL
- https://mcp-simulators.studiox-ai.com/login/xauth
- Token path
- token
- Header name
- X-Auth-Token
- Token prefix
- (empty)
200 naming the user.
Leave the prefix as "Bearer " and it fails on purpose, saying so. A client that hardcodes the prefix cannot talk to this class of API at all.
Expiry and refresh
shared identityRejects any token issued more than 15 seconds ago, so the refresh path runs on demand instead of once an hour.
- Auth
- OAuth client credentials, as above
200 immediately after getting a token; 401 with token_age_seconds once it passes 15. A client that refreshes on 401 recovers by itself.
A client that caches its token and never refreshes fails here permanently. That is the bug this endpoint exists to surface.
Credentials a person grants
Each user signs in. Calls run as them, with their permissions.
Per-user OAuth (each person signs in)
carries a personEvery user connects their own account. Calls run as them, with their permissions โ not as a shared credential.
- Register as
- External MCP server: https://mcp-simulators.studiox-ai.com/mcp
- Auth mode
- OAuth โ each user signs in
- Sign-in URL
- https://mcp-simulators.studiox-ai.com/oauth/authorize
- Token URL
- https://mcp-simulators.studiox-ai.com/oauth/token
- Client ID / secret
- studiox-sim-client / sim-secret-8f2a
- Redirect URI
- anything โ nothing needs registering here
Sign in as alice, call list_orders, get 3 rows. As bob, 3 different rows. As carol, all 9.
9 rows while signed in as alice means the call fell back to the shared credential. It still returns 200 โ the row count is the only signal.
Identities
Three people who see different data, plus a shared service account. That difference is what makes per-user auth visible: ask for orders as alice and you get three rows, as the shared account you get nine. Same call, same 200 โ only the data tells you whose identity arrived.
| Username | Password | Role | Sees | |
|---|---|---|---|---|
alice | alice | alice@simulator.test | ANALYST | EMEA โ 3 orders |
bob | bob | bob@simulator.test | ANALYST | AMER โ 3 orders |
carol | carol | carol@simulator.test | ADMIN | all regions โ 9 orders |
| any client_credentials token | all regions โ 9 orders, as the shared service account | |||
Credentials
| What | Value |
|---|---|
| OAuth client โ StudioX (default) | studiox-sim-client / sim-secret-8f2a |
| OAuth client โ Second client | second-client / sim-secret-b41c |
| API key | sim-api-key-4d9e21 |
| Custom headers | X-Client-Id: sim-client-42 ยท X-Client-Secret: sim-secret-c7f1 |
| Token-type header | X-Sim-Token-Type: OAUTH (or KEYPAIR_JWT) |
Scope for /api/scoped | orders.read |
Login response shapes
The same token under five different key names. This is what the Token Path field is for.
| URL | Response | Token path |
|---|---|---|
/login/token | { "token": "โฆ" } | token |
/login/access_token | { "access_token": "โฆ" } | access_token |
/login/nested | { "data": { "token": "โฆ" } } | data.token |
/login/session | { "result": { "session": { "id": "โฆ" } } } | result.session.id |
/login/xauth | { "token": "โฆ" } | token, header X-Auth-Token, empty prefix |
Extra fields are accepted and echoed back, so you can prove the client sent them:
curl -s -X POST https://mcp-simulators.studiox-ai.com/login/nested \
-H 'content-type: application/json' \
-d '{"username":"alice","password":"alice","dbname":"PROD","dbschema":"PUBLIC"}'Sixty-second check
# Connectivity, no credentials
curl -s https://mcp-simulators.studiox-ai.com/api/open
# A shared token โ note "is_shared_service_account": true, and 9 orders
TOKEN=$(curl -s -X POST https://mcp-simulators.studiox-ai.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=studiox-sim-client \
-d client_secret=sim-secret-8f2a \
| sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
curl -s https://mcp-simulators.studiox-ai.com/api/whoami -H "Authorization: Bearer $TOKEN"
curl -s https://mcp-simulators.studiox-ai.com/api/orders -H "Authorization: Bearer $TOKEN"
# Now as a person โ 3 orders, not 9
ALICE=$(curl -s -X POST https://mcp-simulators.studiox-ai.com/login/nested \
-H 'content-type: application/json' \
-d '{"username":"alice","password":"alice"}' \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -s https://mcp-simulators.studiox-ai.com/api/orders -H "Authorization: Bearer $ALICE"