1. Docs
  2. API Reference
  3. Create a deny

Create a deny

POST/api/v1/denies

Refuses permission_keys to a target at node_id and every node beneath it, checked before any grant: no role held anywhere, and no grant made lower down, can undo it. The target is an identity, a machine client (oauth_client, by its row id), or a role, which binds everyone holding it wherever they hold it. Holders of an exception_role_ids role keep access wherever they hold it, while that assignment is in effect; use one so the people who manage access are not locked out. A reason is required. A deny is never edited: to change one, delete it and create another. Canopy's own routes refuse from the next request; an SDK within its cache window. Preview it first with POST /denies/preview, which takes the same body. Writes a deny.created audit row and webhook event. Requires deny.manage; a person as the target also needs the right to see the Environment's people.

Authentication

Bearer TokenAuthorization Option A

JWT access token. Never send alongside X-API-Key: a request carrying both is refused.

API KeyX-API-Key Option B

API key for management-tier access. Never send alongside an Authorization header: a request carrying both is refused.

Request body

application/json

target_typeenum Required

What the deny targets: one identity, one machine client (`oauth_client`), or every holder of a role, wherever they hold it.

one of "identity" · "oauth_client" · "role"

target_idstring · uuid Required

The identity, the machine client's row id, or the role it targets.

permission_keysstring[] Required

The permission keys it refuses, each in the Environment's catalog.

node_idstring · uuid Required

The node it applies at. It refuses there and at every node beneath, and no grant lower down can undo it.

exception_role_idsstring[] Optional

Roles whose holders keep access despite the deny, wherever they hold them, while that assignment is in effect. Use one so the people who manage access are not locked out.

reasonstring Required

Why the deny exists. Required: it is the record an auditor reads.

max length 500

Responses

application/json

  • dataDenyResponseDto*

application/json

  • errorApiErrorBodyDto*

application/json

  • errorApiErrorBodyDto*

application/json

  • errorApiErrorBodyDto*

application/json

  • errorApiErrorBodyDto*

Returned object

Request
curl -X POST "https://auth.canopy-io.com/api/v1/denies" \
  -H "X-API-Key: $CANOPY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target_type": "identity",
    "target_id": "00000000-0000-0000-0000-000000000000",
    "permission_keys": [
      "documents.view"
    ],
    "node_id": "00000000-0000-0000-0000-000000000000",
    "exception_role_ids": [
      "string"
    ],
    "reason": "Office A files are under legal hold."
  }'
Response
{
  "data": {
    "id": "00000000-0000-0000-0000-000000000000",
    "target": {
      "type": "identity",
      "id": "00000000-0000-0000-0000-000000000000",
      "name": "string",
      "email": "string"
    },
    "permission_keys": [
      "documents.view"
    ],
    "node": {
      "id": "00000000-0000-0000-0000-000000000000",
      "name": "string",
      "path": [
        "string"
      ]
    },
    "exception_roles": [
      {
        "id": "00000000-0000-0000-0000-000000000000",
        "name": "string"
      }
    ],
    "reason": "string",
    "created_by": {
      "type": "user",
      "id": "00000000-0000-0000-0000-000000000000",
      "name": "string"
    },
    "created_at": "2026-04-20T12:00:00.000Z"
  }
}
Related endpoints
GETList denies
POSTPreview a deny
GETGet a deny
DELETEDelete a deny
GETWhat a deny refuses today
Was this page helpful?

Tell us how we can improve this guide.