ON THIS PAGE ▾

Developer Docs

Integrate in 5 minutes. Verify your account once to go live.

Quickstart

Install

pip install permitly
npm install permitly

Minimal working example (Python)

from permitly import PermitlyClient, ScopeTemplates

client = PermitlyClient("sk_live_...")

# Step 1 — request consent, get a URL to redirect your user to
result = client.consent.request({
    "end_user_ref": "user_123",
    "scopes": [ScopeTemplates.send_email()],
    "redirect_url": "https://yourapp.com/done"
})
return redirect(result.consent_url)

# Step 2 — after user approves, verify the token your agent receives
payload = client.consent.verify(token)
# payload.scopes contains the approved scopes

# Step 3 — receive webhook events (Flask example)
from flask import request
from permitly import WebhookVerifier

verifier = WebhookVerifier("whsec_...")

@app.route("/webhook", methods=["POST"])
def webhook():
    event = verifier.verify(request.data, request.headers["X-Permitly-Signature"])
    if event["type"] == "consent.approved":
        # grant the agent access
        pass
    return "", 200
Integrated in 5 minutes. Get your API key from the dashboard. See Account Verification before going live.

Account Verification

You can build and test against the full API immediately after signing up — no verification required to start. Verification is only needed before your agents interact with real end users.

You can build and test against the API immediately after signing up. Before any agent under your account can send a consent request to a real end user, your account needs two things on file — once, not per agent:

  • Domain verification. Claim a domain in the dashboard, then publish a DNS TXT record we give you. DNS changes can take a few hours to propagate — that's normal, just check again later.
  • Payment method on file. Add a card via Stripe Checkout. No charge is made — this is purely an identity/accountability check, required even on the Free plan.

Until both are complete, POST /v1/consent/request returns a 403 with code account_not_verified. Everything else — creating agents, configuring webhooks, generating API keys — works before verification, so you can fully integrate before deciding to go live.

Once verified, your domain is shown on every consent page your agents send, so end users have something concrete to check your agent's identity against. See Trust & Security for why this exists.

Authentication

All API requests require a bearer token. API keys have the prefix sk_live_.

Authorization: Bearer sk_live_abcdef1234567890

Get your key from Settings → API Keys in your dashboard. Keys are shown once — store them securely. We store only the bcrypt hash.

Token Verification

After consent is approved, your agent receives a signed JWT. Verify it server-side before granting access.

POST /v1/consent/verify

{ "token": "eyJhbGci..." }

JWT payload fields

Field Description
sub End user reference (your end_user_ref value)
agt Agent ID
cid Consent request ID (cr_...)
scp Array of approved scope keys
iat Issued at (Unix timestamp)
exp Expires at (Unix timestamp)
jti JWT ID — used for revocation lookup

Python example

payload = client.consent.verify(token)
# Raises PermitlyError on invalid/expired/revoked token

if "calendar.book" in payload.scopes:
    # grant calendar access
    pass

Webhooks

Permitly fires a webhook on every consent lifecycle event. Configure your endpoint in the dashboard under Agents → Webhooks.

Payload shape

{
  "id": "evt_01abc...",
  "type": "consent.approved",  // consent.approved | consent.declined | consent.revoked
  "created_at": "2026-05-26T14:00:00Z",
  "data": {
    "consent_id": "cr_...",
    "agent_id": "ag_calendarbot",
    "end_user_ref": "user_123",
    "scopes": ["calendar.book"],
    "token": "eyJhbGci..."  // only on consent.approved
  }
}

Signature verification

Every request includes X-Permitly-Signature: sha256=<hmac>. Verify it to reject spoofed payloads.

# Python
from permitly import WebhookVerifier
verifier = WebhookVerifier("whsec_...")
event = verifier.verify(request.body, request.headers["X-Permitly-Signature"])
// Node.js
import { WebhookVerifier } from 'permitly'
const verifier = new WebhookVerifier('whsec_...')
const event = verifier.verify(req.rawBody, req.headers['x-permitly-signature'])

Scope Templates

Built-in templates produce a ready-to-use scope dict with sensible defaults. Usage: ScopeTemplates.send_email()

Key Label Risk Reversible
send_email Send email on your behalf HIGH yes
read_email Read email messages HIGH yes
calendar_read Read calendar events MEDIUM yes
calendar_write Create/edit calendar events MEDIUM yes
calendar_book Book appointments LOW yes
files_read Read files and documents MEDIUM yes
files_write Create and edit files HIGH yes
contacts_read Read contact list MEDIUM yes
contacts_write Add or update contacts MEDIUM yes
profile_read Read basic profile info LOW yes

Error Reference

All errors use the same envelope:

{ "error": { "code": "token_expired", "message": "The consent token has expired." } }
HTTP Code Meaning
401 unauthenticated Missing or invalid API key
403 account_not_verified Builder account has not completed domain + payment verification
403 forbidden Lacks permission
404 not_found The requested resource does not exist
422 validation_error Request body failed validation
422 token_expired JWT exp claim has passed
422 token_revoked Consent was revoked after token issued
422 token_invalid JWT signature verification failed
429 rate_limit_exceeded Too many requests — back off and retry
500 server_error Unexpected server error — retry with backoff
503 service_unavailable Permitly is temporarily unavailable — check status page