---
title: "Authorization check"
method: POST
path: "/authz/check"
tags: ["authz"]
---

# Authorization check

`POST /authz/check`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/authz/check`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Evaluates an authorization decision for the caller (or, with `user_id`, for another account the caller may read).

**The `resource` is a description, not a lookup.** Nothing loads the row behind `resource.id`, so the request must carry whatever the rules match on — `owner_id` for the owner baseline, `tenant_id` for any tenant-scoped grant (including every admin-managed type: tenant, connection, email_template, terms, group_type, …). A request naming only `{type, id}` is judged against an empty tenant and an empty owner: it returns `allow:false` with `reason:"resource_unspecified"`, which means the request under-described the resource — not that a tenancy check failed.

Example of a same-tenant read that can actually be allowed:
`{"action":"read","resource":{"type":"account","id":"<id>","tenant_id":"<caller tenant>","owner_id":"<id>"}}`

**Leaf types** (`graph`, `graph_node`, `graph_run`, `graph_skill` — decided by the leaf service that owns them, ADR 0005): v3 cannot know the leaf's own answer (e.g. graph membership), so the check composes your tenant's and your account's deny rules over an ASSUMED leaf allow and returns `baseline:"assumed"`. `allow:true` then means "no policy of yours would refuse this"; whether the leaf allows it is the leaf's question (for graphs, `GET /graphs/{id}/access`).

## Request body

- EnforcerV3InternalUsecasePoliciesEvaluateInput
  - `action` string
  - `contexts` object: Contexts is a free-form payload the policy may key on (size-capped).
  - `policy_id` string: PolicyID selects a stored policy; empty falls through to the built-in static policy (HTTP parity with the gRPC Authorize).
  - `resource` EnforcerV3InternalUsecasePoliciesCheckResource
    - `id` string: ID identifies the resource for logging and for custom policies that key on it. It is NOT resolved to a stored row.
    - `owner_id` string: OwnerID is the owning account. Required for an owner-baseline check.
    - `tenant_id` string: TenantID is the owning tenant. Required for any tenant-scoped grant — including every admin-managed type. Omit it and the check is judged against an empty tenant, not against the caller's.
    - `type` string
  - `user_id` string: SubjectAccountID (user_id) evaluates on behalf of another account — the caller must be able to read it. Empty = evaluate as the caller.

## Response `200`

OK

- InternalDeliveryHttpHandlerAuthzCheckResponse
  - `allow` boolean
  - `baseline` 'assumed': Baseline is "assumed" when the resource type is decided by a leaf service (ADR 0005): the tenant and account layers were composed over an assumed leaf allow, because v3 cannot know the leaf's own answer. Absent otherwise.
  - `policy_id` string
  - `reason` string
  - `success` boolean

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `503`: Service Unavailable

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
