# Scopes and run-as permissions

> Apply Hodoflow's two-layer authorization model and choose the least-privilege scope for each API surface and operation.

Every protected request must pass two independent authorization layers:

1. the service account has the exact API **scope** required by the operation;
2. its active **run-as member** is authorized for the organization, workspace,
   and object.

A broad run-as role cannot compensate for a missing API scope, and a broad
scope cannot bypass the run-as member's product authorization.

## Scope matrix

| Scope | Applies to | Examples |
|---|---|---|
| `api_read` | Safe JSON:API methods and GraphQL `factQuery` | List workflows, retrieve runs, query facts |
| `api_write` | Unsafe JSON:API operations | Create a dashboard, update a data model, activate a workflow |
| `webhooks` | Inbound webhook delivery | `POST /api/v1/webhooks/:token` for the trigger selecting this account |

The public `health` GraphQL field requires no credential. No public GraphQL
mutations or subscriptions are exposed. Each endpoint page in the
[API reference](/reference) displays the exact required scope next to its method
and path.

## Run-as behavior

Choose a real, active organization member whose access represents the
integration's intended owner. Requests execute inside the service account's
stored workspace. Object policies can further restrict a read or write, such as
self-only profile access or owner-only organization changes.

If that membership becomes inactive, the account's requests stop succeeding
even when its bearer token has not expired. Changing a member's role can also
change visible objects and allowed writes immediately.

Use a dedicated integration member where operational ownership needs a stable,
auditable boundary. Do not select the most privileged person merely because it
avoids permission design.

## How failures appear

- `401 unauthorized` means authentication failed: the token is absent, invalid,
  expired, or the underlying account is no longer valid.
- `403 forbidden` or `insufficient_scope` means the identity is known but the
  required scope or policy is not satisfied.
- `404 not_found` can protect object existence when the run-as member cannot see
  a resource. Do not treat it as proof that the object does not exist elsewhere.

Keep the `x-request-id` from any failed response. Never include the bearer token
when sharing diagnostics.

## Least-privilege patterns

- Separate reporting (`api_read`) from automation that writes (`api_write`).
- Grant `webhooks` only to the account selected by the webhook trigger.
- Use separate service accounts for separate workspaces and vendors.
- Remove a scope before deleting an integration so runtime revalidation cuts
  access immediately.
- Review reference pages before adding `api_write`; not every internal action is
  publicly exposed.

Continue with [Perform an idempotent write](/guides/idempotent-write) or
[Sender authentication and signatures](/webhook-ingestion/authentication-signatures).
