Exchange an active service account's client ID and secret at
POST /api/v1/oauth/token. The canonical endpoint does not include an
organization domain: Hodoflow resolves the owning organization from the
server-generated client ID and fails closed if that identity is ambiguous.
JSON request
Build the request body from secret-manager-provided environment variables and stream it to cURL. Do not paste a literal secret into the command or URL.
jq -n '{
grant_type: "client_credentials",
client_id: env.CLIENT_ID,
client_secret: env.CLIENT_SECRET
}' |
curl --silent --show-error \
--request POST \
--header "Content-Type: application/json" \
--data-binary @- \
"$HODOFLOW_BASE_URL/api/v1/oauth/token"
HTTP Basic authentication is also supported for standards-compatible clients. The JSON form above makes the complete contract visible and is used by the first-request guide.
Success response
{
"access_token": "<redacted JWT>",
"token_type": "bearer",
"expires_in": 900
}
expires_in is measured in seconds. The default is 900, but deployments can
configure it. There is no refresh token. A client should:
- retain the bearer token in process memory;
- calculate an early refresh time from the returned lifetime;
- reuse the token until that time instead of exchanging per API call;
- permit one concurrent worker to exchange while peers await its result; and
- discard the token on process shutdown.
The exchange response and token must be redacted from logs. The token is a bearer credential: possession is sufficient until runtime revalidation rejects the underlying account.
Error contract
| Status | Body | Meaning | Recovery |
|---|---|---|---|
400 |
{"error":"unsupported_grant_type"} |
Missing or non-client_credentials grant |
Correct the request; do not retry unchanged |
401 |
{"error":"invalid_client"} |
Missing, invalid, inactive, revoked, or non-promoted credentials | Re-read the configured pair and account state; do not log them |
429 |
API rate-limit error with Retry-After |
Exchange rate window exceeded | Wait the stated number of seconds and coordinate refreshes |
The endpoint intentionally does not disclose whether the client ID, secret, or service account was the invalid part.
Use the token
Send the token only in an HTTPS authorization header:
Authorization: Bearer <token>
Never send it in a query string, cookie, webhook URL, browser storage, or page
source. If an API request returns 401, exchange once with the current client
credentials. Repeated 401 responses require inspecting account activation,
rotation state, and run-as membership rather than an unbounded retry loop.
See the exact token endpoint reference, then learn rotation and revocation.