> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Leases & Revocation

> Time-limited leases let you revoke a viewer's access without re-encrypting content. Configure lease TTL, revoke by viewer or lease ID.

Leases add time-limited access control to content keys. When enabled, the key server issues a lease on first key request and validates it on subsequent requests. Revoking a lease immediately stops playback — without re-encrypting the video.

## Enable leases

Set `LEASE_TTL_MS` to enable:

```bash theme={null}
docker run -d \
  -e MASTER_KEY_HEX=... \
  -e SALT_HEX=... \
  -e CORS_ORIGINS=https://app.example.com \
  -e AUTH_JWKS_URL=https://auth.example.com/.well-known/jwks.json \
  -e LEASE_TTL_MS=300000 \
  -e DATABASE_URL=postgres://user:pass@db:5432/blindcast \
  -p 4100:4100 \
  blindcast/keyserver
```

<Warning>
  Leases require authentication. If neither `AUTH_JWT_SECRET` nor `AUTH_JWKS_URL` is set, leases are disabled regardless of `LEASE_TTL_MS`.
</Warning>

## How leases work

1. **Player calls** `POST /keys/leases` with the content ID and auth token
2. **Key server** extracts the viewer ID from the JWT `sub` claim, creates a lease, and returns the lease ID + TTL
3. **Player sends** `X-Lease-Id` header on every `GET /keys/:contentId` request
4. **Key server validates** the lease on each request — if expired or revoked, returns 403
5. **Player renews** the lease at 75% of TTL via `POST /keys/leases/renew`

## Revoking access

### Revoke by viewer

Revoke all active leases for a viewer (e.g., when they cancel their subscription):

```bash theme={null}
curl -X POST https://keys.example.com/keys/leases/revoke \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <admin-token>" \
  -d '{"viewerId": "user-123"}'
```

### Revoke by lease ID

Revoke a specific lease:

```bash theme={null}
curl -X POST https://keys.example.com/keys/leases/revoke \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <admin-token>" \
  -d '{"leaseId": "abc-123-def"}'
```

### Revoke via database

For bulk operations, update the database directly:

```sql theme={null}
-- Revoke all leases for a viewer
UPDATE leases SET revoked = TRUE WHERE viewer_id = 'user-123';

-- Revoke all leases for a piece of content
UPDATE leases SET revoked = TRUE WHERE content_id = 'video-456';
```

## What happens when a lease is revoked

1. The player's next key request (or lease renewal) gets a `403` response
2. The player emits a `KEY_LEASE_EXPIRED` error
3. Playback stops — the player cannot fetch new keys

See [Player Leases](/player/leases) for how to handle this in your UI.

## Configuration

| Variable       | Default                       | Description                                               |
| -------------- | ----------------------------- | --------------------------------------------------------- |
| `LEASE_TTL_MS` | —                             | Lease TTL in milliseconds. If unset, leases are disabled. |
| `DATABASE_URL` | `sqlite:///data/blindcast.db` | Where leases are stored                                   |

## Cleanup

Expired leases accumulate over time. The key server runs a background cleanup task every hour, deleting leases that expired more than 24 hours ago.

For Postgres deployments, you can also run cleanup manually:

```sql theme={null}
DELETE FROM leases WHERE expires_at < NOW() - INTERVAL '24 hours';
```
