Browse documentation
Guide

Service accounts

Create a client ID and secret for a machine, scope what it can do, decide whose permissions it runs with, and rotate it without downtime.

View as Markdown

A service account is how something that is not a person authenticates with your organization. A webhook sender, a script that reads records, a page embedding a dashboard — each gets its own credential, its own scopes, and its own audit trail.

They live at Settings → Service Accounts. Creating and editing one, and viewing its credentials, is limited to organization owners and admins.

Create one

New Service Account asks for three things.

Name is for you. Name it after the integration that will hold it — Stripe webhook ingest — so that revoking the right one later is obvious.

Scopes decide what the credential is allowed to do. At least one is required.

Scope Grants
webhooks Sending data into Hodoflow through webhook requests.
api_read Read-only REST access to workflows, runs, and warehouse records.
api_write Any JSON API request that is not a read.
embed Loading dashboards inside external sites through the embed channel.

Grant the narrowest set that works. A webhook sender needs webhooks and nothing else.

Run as is the one that surprises people. A service account does not have permissions of its own — it borrows a member's. Pick an organization member and every request authenticated with this credential executes as them, respecting their permissions. Only active members of the current organization are offered.

That means deactivating a person can change what a service account can reach. Point machine credentials at whoever should own the integration long-term.

The client ID and secret

Creating the account mints a pair: a client ID beginning cid_ and a client secret beginning csec_. Neither embeds anything about your organization, so neither is safe to treat as public.

Client Credentials on the service account's page shows both, each with a Copy button. Unlike most secrets, these stay retrievable — come back to this page whenever an integration needs re-configuring. Access is limited to owners and admins.

Getting an access token

The pair is not what you send with a request. Exchange it for a short-lived token first:

curl -X POST https://app.example.com/api/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "cid_...",
"client_secret": "csec_..."
}'

You can also send the pair as HTTP Basic auth instead of in the body. The response is the token, its type, and its lifetime in seconds:

{"access_token": "eyJ...", "token_type": "bearer", "expires_in": 900}

Fifteen minutes, so a long-lived integration exchanges again rather than caching the token forever. Send it as Authorization: Bearer <access_token>.

A token carries the scopes and the run-as member that were set when it was issued, which is why a scope change takes effect on the next exchange rather than instantly.

Every response for a credential problem is the same 401 with {"error": "invalid_client"} — missing credentials, wrong secret, unknown client, and a deactivated account all look identical from the outside. That is deliberate. Check the service account's status in Hodoflow rather than reading the error.

Rotating the secret

Rotation is two steps, so there is no window where nothing works.

Stage new secret generates a second client ID and secret alongside the active pair. Both pairs authenticate at once. Copy the staged values from Client Credentials, where they appear under Staged credentials with a Pending promotion badge, and update your integrations.

Promote staged then makes the staged pair the active one, and the previous pair stops working immediately. Do it once your integrations are switched over and you have confirmed they still work.

Restage new secret replaces a staged pair you have not promoted yet. The pair you overwrite stops working immediately, so only restage if nothing has picked the staged values up.

Turning one off

Deactivate stops the credential from exchanging for tokens at all. It is reversible with Activate, which makes it the right tool for a suspected leak you are still investigating.

Deactivation is refused while an active workflow depends on the credential — deactivate the workflow first. Usage on the service account's page lists the workflow versions whose triggers authenticate with it, so you know which ones.

Delete is permanent, and any external system using the credential loses access the moment you confirm. It is refused while workflow triggers still reference it.

  • Triggers and webhooks — a webhook trigger requires a service account holding the webhooks scope.
  • Embeds — dashboard embeds use a share token, not a service account.
  • The developer portal covers the exchange and the token's claims in detail at authentication.