GrantLayer is access-grant and authorization infrastructure: an open-source, multi-tenant system for issuing time-boxed access grants to AI agents and automated systems, enforcing them through a policy engine, and recording every decision in a cryptographically verifiable audit trail. A grant is a permission — who may perform which action on which resource, for how long — and every issue, approval, denial, revocation, and exercise is traceable, tamper-evident, and independently auditable. It is in Developer Preview — local evaluation and controlled pilot only. It is not production SaaS; production SaaS readiness is not claimed. No GitHub push and no repository visibility change are part of this local quickstart.
| Area | Status |
|---|---|
| Production SaaS readiness | Not claimed |
| Tenant/workspace isolation | Enforced at API level — workspace-scoped queries, cross-workspace denial |
| Public GitHub release | Available |
| Real customer data in examples | None; examples use synthetic/demo data only |
| Real secrets in examples | None; use placeholders or generated local values only |
Production SaaS readiness is not claimed. Tenant/workspace isolation is enforced at the API level: every request resolves a workspace_id and tenant_id from the operator identity; queries are server-side scoped to that workspace and tenant; cross-workspace access is denied by default. This provides strong isolation for developer preview and controlled pilot deployments. Examples use no real secrets and no real customer data.
- Path A: run the first verifiable output quickstart below. It requires no backend and uses Python stdlib only.
- Path B: run the backend quickstart with Docker Compose or local Python setup.
Get the stack running in under 5 minutes:
git clone https://github.com/Discodone/grantlayer.git
cd grantlayer
cp .env.example .env # local-eval defaults boot as-is (JWT keys for API calls: QUICKSTART.md step 1)
./nginx/generate-certs.sh # self-signed TLS for local dev
# Provision the schema with Alembic. PostgreSQL is the default backend and the
# app does not self-provision it — skip this and the API fails to start.
docker compose up -d db
docker compose run --rm api python3 -m alembic -c backend/alembic.ini upgrade head
docker compose up -d
curl -k https://localhost/healthSee QUICKSTART.md for the full walkthrough: token generation, creating grants, grant requests, and audit log export.
The stack defaults to GRANTLAYER_RUNTIME_MODE=local for evaluation; set production in .env to enable the hardened production gates (see DEPLOYMENT.md). The Cardano anchoring worker is optional and starts only with docker compose --profile anchoring up -d after creating the secrets files described in secrets/README.md — the default stack requires neither.
See CHANGELOG.md for public snapshot version anchors and caveats.
Run the first verifiable output quickstart:
python3 examples/first_verifiable_output.py --output /tmp/grantlayer_first_output.jsonThe generated file is /tmp/grantlayer_first_output.json. The committed deterministic reference output is examples/first_verifiable_output.json; see docs/first_verifiable_output.md.
This path is local/demo only, requires no real secrets, requires no customer data, uses no real secrets, and uses no real customer data.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness check |
GET |
/v1/grants |
List all grants |
POST |
/v1/grants |
Create a grant |
POST |
/v1/grants/:id/revoke |
Revoke a grant |
GET |
/v1/grant-requests |
List grant requests |
POST |
/v1/grant-requests |
Submit a grant request (requires GRANTLAYER_ENABLE_OPERATOR_MODEL=true) |
POST |
/v1/grant-requests/:id/approve |
Approve a grant request |
POST |
/v1/grant-requests/:id/deny |
Deny a grant request |
POST |
/v1/exercise |
Exercise a grant — records the policy-checked decision as an audit event (/v1/demo-action is a legacy alias that 307-redirects here) |
GET |
/v1/audit-events |
List audit events |
GET |
/v1/grant-executions |
List grant executions (owner/v1/admin/v1/auditor) |
Full OpenAPI spec: docs/openapi.yaml. Interactive Swagger UI available at /api/docs when the stack is running.
GrantLayer is a Python/FastAPI backend served behind an Nginx TLS reverse proxy. Storage is PostgreSQL 16 in the Docker Compose stack (the compose default) or SQLite when running bare locally with GRANTLAYER_DATABASE_URL unset. All grants are signed with Ed25519 and form a tamper-evident audit chain — each event records what was decided, by whom, and when. The operator model provides a request/approval workflow: subjects submit grant requests, and operators approve or deny them. JWT authentication guards all API endpoints — RS256 by default (base64-PEM keypair via GRANTLAYER_JWT_PRIVATE_KEY/GRANTLAYER_JWT_PUBLIC_KEY), with legacy HS256 when only GRANTLAYER_JWT_SECRET is set; tokens encode the caller's subject, tenant, role, and expiry. Docker Compose brings up the API, Nginx, and PostgreSQL as a single docker compose up command.
Audit-chain scope — the un-anchored window. The optional on-chain anchoring commits to the audit chain only as it stood when an anchor was published. Events appended after the newest anchor sit in an un-anchored window: the row-hash chain alone cannot expose a database-level rewrite of that window (truncating to an earlier prefix, re-linking the chain after an edit, or mutating a field and re-hashing in place all yield a chain that still verifies), and no on-chain commitment covers those events until the next anchor is published. Anchors are deliberately cadence-free — each is an explicit operator act, not a scheduled job — so the window has no fixed length. This is the structural trade-off of periodic anchoring, not a defect: an anchor bounds what the past can be rewritten to, it does not protect what has not yet been anchored.
| Variable | Default | Description |
|---|---|---|
GRANTLAYER_RUNTIME_MODE |
local (Docker Compose) / production (when unset) |
Runtime mode. Valid values: local, demo, test, staging, production |
GRANTLAYER_JWT_SECRET |
(unset) | Shared secret enabling legacy HS256 JWTs; without it the default is RS256 via GRANTLAYER_JWT_PRIVATE_KEY/GRANTLAYER_JWT_PUBLIC_KEY (base64-encoded PEM) |
GRANTLAYER_ENABLE_OPERATOR_MODEL |
true |
Enable the grant request / approval workflow |
GRANTLAYER_ENABLE_DEMO_ENDPOINTS |
false |
Enable demo tamper endpoints (never in production) |
GRANTLAYER_DATABASE_URL |
(empty = SQLite) | PostgreSQL URL when using the postgres profile |
GRANTLAYER_HOST |
0.0.0.0 |
Bind address |
GRANTLAYER_PORT |
8765 |
HTTP port (inside container) |
Copy .env.example to .env — the local-evaluation defaults boot as-is. Calling the API requires JWT material: follow QUICKSTART.md step 1 (RS256 keys) or set legacy GRANTLAYER_JWT_ALGORITHM=HS256 plus GRANTLAYER_JWT_SECRET. Every variable set in .env is forwarded into the containers.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export GRANTLAYER_RUNTIME_MODE=local
export GRANTLAYER_JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
python3 -m backendThis starts the same FastAPI/uvicorn server as docker compose up. Backend starts at http://127.0.0.1:8765.
GRANTLAYER_RUNTIME_MODE=local selects local-evaluation mode (SQLite auto-provisioning, relaxed gates). Valid values are local, demo, test, staging, production; when the variable is unset the server defaults to production, which deliberately refuses to start without Redis, a strong admin token, and the other hardening settings listed in DEPLOYMENT.md.
Equivalent direct invocation:
uvicorn backend.src.api.app:app --port 8765python3 -m pytest backend/tests/ -q -m "not doc_guard"Or via script: ./scripts/test.sh
See CONTRIBUTING.md for coding guidelines, test expectations, and the DCO recommendation. Security reports go to SECURITY.md.
No mature public contribution process is claimed yet — this is a developer preview.
Apache License 2.0 — see LICENSE.