Browse documentation
Concept Authentication

Exchange service-account credentials

Call the client-credentials token endpoint safely, interpret its response, and cache short-lived bearer tokens without a refresh token.

View as Markdown

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:

  1. retain the bearer token in process memory;
  2. calculate an early refresh time from the returned lifetime;
  3. reuse the token until that time instead of exchanging per API call;
  4. permit one concurrent worker to exchange while peers await its result; and
  5. 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.

Related documentation