Developers
Granite · integration

File approval requests, learn the outcome

Your app or agent asks the user for permission through Granite. The user decides; you receive the outcome correlated by your own requester_user_ref — never their email, never a shared identifier.

The model

01

Register an app

Operator-issued today — email support@ to get set up.
support@
02

Receive an app credential

A bearer secret, scoped to your app — sent with x-granite-app-id.
app_key
03

File approval requests

One request per decision you need from the user.
POST /v1/approval-requests
04

The user decides in Granite

Approve or deny — high risk takes a deliberate hold.
in-app
05

Learn the outcome

Decision webhook to your callback URL, or poll the request.
webhook · poll
06

Verify standing grants before acting

Confirm a grant is still active each time you rely on it.
POST /v1/grants/verify

File a request

Describe what you want, where it reaches, and the risk you assess. Granite renders it to the user and holds the pending decision until they act or it expires. App-filed requests start unbound: Granite binds one to the user when they sign in to decide — pairwise identity means you and Granite name the same person differently, by design.

Request · curl
POST /v1/approval-requestsrequest
# your app credential + your own pairwise id for the user
curl https://granitegranted.com/v1/approval-requests \
  -H "Authorization: Bearer $GRANITE_APP_SECRET" \
  -H "x-granite-app-id: atlas" \
  -H "x-user-id: u_8Q3K2M11XZ" \
  -H "Content-Type: application/json" \
  -d '{
    "request_type":       "delegation_grant",
    "title":              "Act on your behalf",
    "summary":            "Atlas wants to let other agents act for you.",
    "risk_level":         "high",
    "requested_action":   "let other agents act on your behalf",
    "requested_resource": "delegation",
    "requested_scopes":   ["read", "send", "re-delegate"],
    "callback_url":       "https://atlas.example/granite/webhook"
  }'
201 Createdresponse
{
  "id":                 "2c8be6d4-d657-48ef-b3f5-923c4ed975df",
  "owner_uid":          null,                // unbound until the user signs in
  "requester_user_ref": "u_8Q3K2M11XZ",   // your x-user-id, echoed
  "requester_app_id":   "atlas",           // bound by Granite, not you
  "request_type":       "delegation_grant",
  "risk_level":         "high",
  "status":             "pending",
  "created_at":         "2026-06-09T14:21:07Z"
}

Learn the outcome

When the user decides, Granite POSTs to your callback URL (register a callback_secret and the body is HMAC-signed). Delivery is fire-and-forget today, so treat the webhook as a nudge and poll as the source of truth.

Decision webhook · received at your callback
POST https://atlas.example/granite/webhookpayload
{
  "event":              "approval.decided",
  "request_id":         "2c8be6d4-d657-48ef-b3f5-923c4ed975df",
  "decision":           "approved",
  "status":             "approved",
  "requester_user_ref": "u_8Q3K2M11XZ",
  "requester_app_id":   "atlas",
  "trace_id":           null,
  "decided_at":         "2026-06-09T14:23:40Z"
}
Or poll · curl
GET /v1/approval-requests/{id}request
curl https://granitegranted.com/v1/approval-requests/2c8be6d4-d657-48ef-b3f5-923c4ed975df \
  -H "Authorization: Bearer $GRANITE_APP_SECRET" \
  -H "x-granite-app-id: atlas" \
  -H "x-user-id: u_8Q3K2M11XZ"
# → { "status": "approved", "grant_id": "…", ... }

Verify before acting

A standing grant can be revoked by the user at any time. Verify it is still active immediately before you rely on it — do not cache the answer.

Verify a grant · curl
POST /v1/grants/verifyrequest
curl https://granitegranted.com/v1/grants/verify \
  -H "Authorization: Bearer $GRANITE_APP_SECRET" \
  -H "x-granite-app-id: atlas" \
  -H "x-user-id: u_8Q3K2M11XZ" \
  -d '{ "grant_id": "7c01…", "resource": "delegation", "scopes": ["send"] }'
200 OKresponse
{
  "approved": true,
  "grant": {
    "id":     "7c01…",
    "status": "active",
    "scopes": ["read", "send", "re-delegate"]
  }
}
# revoked / expired / scope-mismatch → { "approved": false, "reason": "…" }

Identity you receive

You getYour own pairwise requester_user_ref, the decision, granted scopes, and a trace id for the record.
You neverNo email, no real name, no phone — and no cross-context identifier shared with any other requester.

Get credentials

Operator-issued today

Credentials are issued by hand while Granite is invitation-stage. Email support@granitegranted.com to register your app — there is no self-serve signup yet; a person reads and replies.