---
title: "Permit.io"
description: "Use Permit.io for managed MainSDK access with roles, attributes and relations - setup, main.yaml, testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/permit-io"
updated: "2026-10-28"
---

# Permit.io

> Use Permit.io for managed MainSDK access with roles, attributes and relations - setup, main.yaml, testing and swapping.

Canonical: https://mainsdk.dev/docs/integrations/permit-io · Updated: 2026-10-28

Permit.io is a hosted permissions service with a dashboard where non-developers can manage roles and rules. Behind [`access`](https://mainsdk.dev/docs/interfaces/access), your app keeps one way to check permissions while policies are managed in Permit.io. Pick it when product or support teams need to change who can do what without a code change.

## Interfaces it serves

### Permissions (`access`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `permit` (Permit.io)
- Docs: https://mainsdk.dev/docs/interfaces/access.md

```yaml
access:
  driver: permit
```

Swap to another provider: `cedar`, `opa`, `openfga`, `spicedb`, `cerbos`, `oso`, `avp`.

## Before you start

1. **Create a Permit.io account and project**, with an environment per stage of your app.
2. **Copy the environment's API key** into your secrets manager.
3. **Set up the decision point** Permit.io's docs recommend for your deployment, reachable from your app.

## Configure

```yaml title="main.yaml"
access:
  driver: permit
  models: [rbac, abac, rebac]   # mainsdk check compares this with the driver's capability profile
  unreachable: { deny: true, cache: 30s }
  # [TBD] connection keys; suggested secret: PERMIT_API_KEY
```

## Use it

```ts title="lib/permissions.ts"
import { access } from "mainsdk";

export const canInvite = (user: { id: string }, org: { type: string; id: string }) =>
  access.can(user, "members.invite", org);
```

## Local development and testing

Build on in-process Cedar with `mainsdk dev`. `createTestApp()` from `@mainsdk/testing` puts `access` on its fake, and `chaos.on("access", { outage: true })` shows checks denying with `MAIN-ACCESS-0007`.

## What MainSDK adds on top

- Deny by default, with no bypass path.
- Denies when Permit.io is unreachable, with only a short cache you set.
- Lists filtered at the database, not after fetching rows.
- Every decision written to the audit log with the policy version.

See [Permissions](https://mainsdk.dev/docs/interfaces/access).

## Limits and notes

- The `access` spec lists Permit.io as a driver that comes later in phases 1-2 through the driver kit, beyond its capability profile.
- Which models the driver supports is declared in its capability profile; `mainsdk check` names any gap with `MAIN-ACCESS-0004`.
- `access.raw()` returns the Permit.io client; `mainsdk check` lists every use.

## Swap to or from it

```yaml title="main.yaml"
access:
  driver: cedar   # was permit
```

> **Warning:** Roles, rules and relationships live in Permit.io. Moving them means exporting them and porting to the new engine; run `mainsdk test access` on the new driver first.

## FAQ

**Who can change permissions?** Whoever has access to your Permit.io project. Every decision is still audited with its policy version in your app.

## Next

- [Permissions](https://mainsdk.dev/docs/interfaces/access)
- [OpenFGA](https://mainsdk.dev/docs/integrations/openfga) and [Oso](https://mainsdk.dev/docs/integrations/oso)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
