> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.thepennyinc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.thepennyinc.com/_mcp/server.

# Access Control

Penny's access model has two kinds of principal — **Users** (people) and **Applications** (machine-to-machine clients). Each principal holds **permissions**, either directly or through **roles**. A role is a reusable bundle of permissions; it is not a principal and cannot authenticate.

## Permissions

A permission is a `resource` + `action` pair:

**`A permission`**

```json title="A permission"
{
  "resource": "account",
  "action": "read"
}
```

Every permission you grant is scoped to your own business.

**Resource** is one of:

| Area       | Resources                                                                                                                                     |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Business   | `business`, `sub_business`, `business_entity`, `business_defaults`, `program`                                                                 |
| Access     | `user`, `role`, `permission`, `application`, `verification`                                                                                   |
| Banking    | `account`, `ledger`, `transaction`, `statement`                                                                                               |
| Issuing    | `card`, `cardholder`, `card_product`, `bin`, `spend_control`                                                                                  |
| Billing    | `fee`                                                                                                                                         |
| Platform   | `task`, `approval`, `document`, `documentation`, `analytics`, `notification_endpoint`, `notification_event`, `notification_decision_exchange` |
| Simulation | `simulation`                                                                                                                                  |
| Wildcard   | `*` — every client resource listed above                                                                                                      |

**Action** is one of:

| Action    | Grants                                                                                                                                            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read`    | Retrieve a single resource.                                                                                                                       |
| `list`    | List resources.                                                                                                                                   |
| `create`  | Create a resource.                                                                                                                                |
| `update`  | Change a resource, including activating, deactivating, and attaching or detaching related objects.                                                |
| `delete`  | Delete, archive, or terminate a resource.                                                                                                         |
| `execute` | Run an operation, such as a simulation.                                                                                                           |
| `reveal`  | Disclose a sensitive value, such as a card's full payment details.                                                                                |
| `act_as`  | Call Penny on behalf of another business. Penny grants this for [multi-business access](/platform/multi-business); you cannot assign it yourself. |
| `*`       | Every action available to you on the resource, except `reveal` and `act_as`.                                                                      |

`reveal` and `act_as` are never included in a wildcard. Grant them explicitly, and only to the principals that need them.

Each endpoint in the API reference lists the permission it requires.

You can grant a permission — directly, or by adding it to a role — only if you hold it yourself. A request that grants a permission you do not hold fails with `422 Unprocessable Entity`.

## Roles

A role bundles permissions so you can assign a named set instead of managing individual grants on each principal. Penny provides two kinds of role:

| Role type | ID prefix      | Ownership                                                                | When to use it                                                                              |
| --------- | -------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| Preset    | `global_role_` | Created and maintained by Penny. You can assign it but cannot change it. | A standard access profile, such as an administrator, finance, developer, or read-only role. |
| Custom    | `role_`        | Created and maintained by your business.                                 | A combination of permissions that no preset provides.                                       |

Preset and custom roles are assigned in exactly the same way. A preset role grants access only within your own business, the same as a custom role.

### Preset roles

Discover the preset roles available to your business rather than hardcoding a list. Names and permission sets can change as Penny adds presets.

```http
GET /access/roles/global
GET /access/roles/global/{role_id}
```

Inspect a preset's `permissions`, then assign its `role_id` to a user or application.

### Custom roles

Create a custom role with `POST /access/roles/`:

**`POST /access/roles/`**

```json title="POST /access/roles/"
{
  "name": "Finance auditor",
  "description": "Allows finance staff to review transactions and ledger entries.",
  "permissions": [
    { "resource": "transaction", "action": "read" },
    { "resource": "transaction", "action": "list" },
    { "resource": "ledger", "action": "list" }
  ]
}
```

Use a preset role when it matches the job function. Create a custom role when you need a stable combination that no preset provides, and use direct permissions only for narrow exceptions.

## Assigning access to users and applications

Users and applications can hold both directly granted `permissions` and a set of `role_ids`. Their effective access is the union of both.

| Principal   | Maximum roles |
| ----------- | ------------- |
| Application | 10            |
| User        | 5             |

`role_ids` accepts both preset (`global_role_…`) and custom (`role_…`) role IDs.

### Setting the full set

* `POST` on a user or application sets its initial `permissions` and `role_ids`.
* `PATCH` on a user, application, or role **replaces** the whole `permissions` set (and, for users and applications, the whole `role_ids` set) with what you send. It does not merge. Send an empty list to remove everything; omit the field to leave it unchanged.

Because `PATCH` replaces the set, two concurrent updates can overwrite each other, and adding one permission requires sending the full list.

### Adding or removing one grant

To add or remove a single role or permission without sending the full set, use the incremental endpoints:

```http
POST   /access/applications/{application_id}/roles/{role_id}
DELETE /access/applications/{application_id}/roles/{role_id}
POST   /access/applications/{application_id}/permissions/{resource}/{action}
DELETE /access/applications/{application_id}/permissions/{resource}/{action}
```

The same endpoints exist under `/access/users/{user_id}/...`. Custom roles support incremental permission changes too:

```http
POST   /access/roles/{role_id}/permissions/{resource}/{action}
DELETE /access/roles/{role_id}/permissions/{resource}/{action}
```

Attaching a role or permission that is already present, or detaching one that is already absent, succeeds without making a change.

### When changes take effect

Changes to roles and permissions can take up to about **5 minutes** to apply to requests. Access tokens issued before the change keep working until they expire.

## Rotate application secrets

An application's `client_secret` is returned once, in the response to `POST /access/applications/`. Penny does not store it in a retrievable form and does not return it on `GET` or `PATCH`. If you lose it, or suspect it was exposed, rotate it:

```http
POST /access/applications/{application_id}/rotate-secret
```

Rotation returns a new one-time secret. The old secret stops working immediately, so it can no longer be used to request tokens. Access tokens already issued with the old secret stay valid until they expire.