Authenticate to the API
Send an API key, exchange it for an access token, use OAuth client credentials, and refresh tokens for people.
On this page
Every call to https://api.hexelstudio.com/v1/... must say who is calling. Scripts, servers and CI jobs authenticate as a service user, with an API key or an OAuth client. People sign in to Hexel Studio with their Hexel account, and the console gets tokens for them.
| Method | Best for | What you send to the API |
|---|---|---|
| API key in a header | Scripts, quick tests, low request rates | X-API-Key: $API_KEY |
| API key exchanged for a token | Services that make many calls | Authorization: Bearer $ACCESS_TOKEN |
| OAuth client credentials | Services and tools that speak OAuth 2.0 | Authorization: Bearer $ACCESS_TOKEN |
| Role session | Temporary access to a role's permissions | Authorization: Bearer $ACCESS_TOKEN |
An access token is a short-lived signed token for one identity in one organization. Tokens are issued by the token service at https://sts.hexelstudio.com. See Tokens for lifetimes and claims.
Send an API key directly
Send the key in the X-API-Key header. Hexel Studio checks the key at the edge, exchanges it for an access token of the key's service user, and passes the request on with that token.
curl https://api.hexelstudio.com/v1/workspaces \
-H "X-API-Key: $API_KEY"- The organization comes from the key. You don't need
X-Organization-Id. If you send it, it must be the key's organization. - If a request carries both
Authorization: Bearer ...andX-API-Key, the bearer token is used and the key is ignored. - Hexel Studio removes
X-API-Keyfrom the request before it reaches the service that handles it. - Some product APIs accept only a bearer token. If a key gets
401on a route where a token works, exchange the key first.
Errors from the API key check have this shape:
{"error": {"code": "403", "message": "X-Organization-Id does not match the API key's organization."}}| Status | Cause |
|---|---|
401 | The key is unknown, expired or revoked, or its service user is disabled or deleted. |
403 | X-Organization-Id names a different organization than the key's. |
429 | Too many invalid API keys from your IP address (20 a minute), or a rate limit was exceeded. Wait for the Retry-After seconds. |
503 | API key checking is temporarily unavailable. Retry with backoff. |
Exchange an API key for an access token
POST https://sts.hexelstudio.com/token with the key in X-API-Key. The body is optional for this grant. If you send one, set grant_type to api_key.
curl -X POST https://sts.hexelstudio.com/token \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"grant_type": "api_key"}'{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "",
"token_type": "Bearer",
"expires_in": 900
}The access token. Send it as Authorization: Bearer <access_token>.
Always empty for this grant. Exchange the key again when the token expires.
Always Bearer.
Seconds until the token expires: 900 (15 minutes).
Then call the API with the token:
curl https://api.hexelstudio.com/v1/workspaces \
-H "Authorization: Bearer $ACCESS_TOKEN"An unknown, expired or revoked key, or a key whose service user is disabled, gets 401 with {"error": "invalid api key"}.
Use OAuth client credentials
An OAuth client is a client_id and client_secret pair that belongs to a service user. Exchange it for an access token with the OAuth 2.0 client_credentials grant.
curl -X POST https://sts.hexelstudio.com/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "'"$CLIENT_ID"'",
"client_secret": "'"$CLIENT_SECRET"'"
}'const res = await fetch("https://sts.hexelstudio.com/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "client_credentials",
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
}),
});
if (!res.ok) {
throw new Error(`Token request failed: ${res.status} ${await res.text()}`);
}
const { access_token, expires_in } = await res.json();import os
import requests
resp = requests.post(
"https://sts.hexelstudio.com/token",
json={
"grant_type": "client_credentials",
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"],
},
timeout=10,
)
resp.raise_for_status()
access_token = resp.json()["access_token"]client_credentials.
The OAuth client's ID, for example client_....
The client secret, shown once when you create or rotate the client.
The token endpoint accepts a JSON body (Content-Type: application/json) or a form body (Content-Type: application/x-www-form-urlencoded). Instead of the body, you can send the client ID and secret with HTTP Basic authentication (Authorization: Basic ..., the ID and secret form-URL-encoded first, as RFC 6749 describes). Use one method: credentials in both places are refused with 400.
The response has the same fields as the API key exchange: a 15-minute access token, token_type Bearer, expires_in 900 and an empty refresh_token. Request a new token when it expires.
| Status | Body | Cause |
|---|---|---|
400 | {"error": "client_id and client_secret required"} | A field is missing. |
400 | {"error": "use one client authentication method: HTTP Basic or the request body"} | Credentials sent both ways. |
401 | {"error": "invalid client credentials"} | Unknown, revoked or expired client, wrong secret, or the service user is disabled. |
Get a role session directly
With an API key or client credentials, you can ask for a session of a role in the same call. Add resource with the role ID. The role's trust policy must allow your service user.
curl -X POST https://sts.hexelstudio.com/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "'"$CLIENT_ID"'",
"client_secret": "'"$CLIENT_SECRET"'",
"resource": "<role-id>",
"session_name": "nightly-export"
}'The response is a role session, described in Assume a role. People and service users that already hold an access token use token exchange instead, on the same page.
People: sign in with a Hexel account
People sign in to the console with their Hexel account. The console exchanges the sign-in for a Hexel Studio access token and a refresh token for the organization you select, and refreshes it for you.
Exchanging a Hexel account sign-in for a token from your own application is not open for self-service. Contact Hexel Studio if you need it. For automation, use a service user.
Refresh tokens
Only a sign-in with a Hexel account returns a refresh token. API keys and OAuth clients never do: exchange the credential again instead. Role sessions can't be refreshed: assume the role again.
To refresh, POST https://sts.hexelstudio.com/refresh with the refresh token:
curl -X POST https://sts.hexelstudio.com/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "'"$REFRESH_TOKEN"'"}'{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "<new-refresh-token>",
"token_type": "Bearer",
"expires_in": 900
}- Each refresh token works once. The response carries a new one; store it and discard the old one.
- A refresh keeps the organization. If you send
X-Organization-Idwith a different organization, the request fails with400. Sign in again to switch. - A refresh fails with
401after the refresh token's 30-day lifetime, after the sign-in session's deadline, or once the account is suspended. It fails with403if the person is no longer an active member of the organization. expires_incan be under 900 when the sign-in session ends sooner.
To sign out, POST https://sts.hexelstudio.com/revoke with {"refresh_token": "..."}. This ends every refresh token of that person's account, in every organization, and answers {"revoked": true}. Access tokens already issued stay valid until they expire. See Tokens for lifetimes and the sign-in deadline.
Token endpoint limits
POST /token issues at most 60 tokens a minute for each credential: each API key, each OAuth client, each person signing in, and each user, service user or role that exchanges an access token for a role session. The count resets at the start of each minute. Over the limit, you get:
{"error": "rate_limited", "error_description": "too many tokens issued for this credential; retry later"}with status 429 and a Retry-After header in seconds. Cache tokens and reuse them until they are close to expiry. See Limits for the API's other rate limits.