Developers
Developers
This page documents the raw HTTP API for building custom integrations with OpenApe. For most use cases, use the grapes CLI instead.
Prerequisites
- A running OpenApe IdP (see Quick Start)
- The IdP's Management Token (set via
NUXT_OPENAPE_IDP_MANAGEMENT_TOKEN) grapesCLI installed:npm i -g @openape/grapes
The Quick Way (grapes CLI)
For most use cases, the grapes CLI handles the entire flow:
# 1. Generate agent key
ssh-keygen -t ed25519 -f ~/.ssh/agent_key -N ""
# 2. Enroll (admin operation — still requires curl or the IdP admin UI)
curl -X POST https://id.example.com/api/agent/enroll \
-H "Authorization: Bearer <management-token>" \
-H "Content-Type: application/json" \
-d "{\"email\":\"agent+deploy@example.com\",\"name\":\"deploy-bot\",\"publicKey\":\"$(cat ~/.ssh/agent_key.pub)\"}"
# 3. Login as agent
grapes login --idp https://id.example.com --key ~/.ssh/agent_key --email agent+deploy@example.com
# 4. Request a grant and execute
grapes run escapes "systemctl restart nginx" --reason "Deploy hotfix #42"
# → Requests grant... waiting for approval... approved! Executing.
That's it. The rest of this page documents the raw API for advanced integrations.
Raw API Reference
curl to show the raw HTTP API. Use these when building your own integration or when grapes doesn't fit your workflow. All examples use https://id.example.com as the IdP URL.Step 1: Generate an Ed25519 Key Pair
The agent needs an Ed25519 key pair for authentication. The public key is registered with the IdP, the private key stays with the agent.
# Generate a new Ed25519 key pair (no passphrase for automation)
ssh-keygen -t ed25519 -f ~/.ssh/agent_key -N ""
# View the public key (this is what you register with the IdP)
cat ~/.ssh/agent_key.pub
# Output: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@host
Step 2: Enroll the Agent
Register the agent with the IdP using the Management Token.
PUBLIC_KEY=$(cat ~/.ssh/agent_key.pub)
curl -X POST https://id.example.com/api/agent/enroll \
-H "Authorization: Bearer my-secret-management-token" \
-H "Content-Type: application/json" \
-d "{
\"email\": \"agent+deploy@example.com\",
\"name\": \"deploy-bot\",
\"publicKey\": \"$PUBLIC_KEY\"
}"
Response:
{
"agent_id": "a1b2c3d4-...",
"email": "agent+deploy@example.com",
"name": "deploy-bot",
"owner": "admin@example.com",
"approver": "admin@example.com",
"status": "active"
}
publicKey must be in SSH format, starting with ssh-ed25519. The owner and approver default to the admin — these are the humans who can approve grants for this agent.Step 3: Authenticate the Agent
Authentication uses a challenge-response flow with Ed25519 signatures.
3a. Request a Challenge
curl -X POST https://id.example.com/api/agent/challenge \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent+deploy@example.com"}'
Response:
{
"challenge": "a3f8b2c1d4e5f6..."
}
3b. Sign the Challenge and Authenticate
CHALLENGE="a3f8b2c1d4e5f6..."
# Sign the challenge with the agent's private key
SIGNATURE=$(echo -n "$CHALLENGE" | ssh-keygen -Y sign -f ~/.ssh/agent_key -n challenge - 2>/dev/null | base64 -w0)
# Alternative: using openssl
SIGNATURE=$(echo -n "$CHALLENGE" | openssl pkeyutl -sign -inkey ~/.ssh/agent_key -rawin | base64 -w0)
curl -X POST https://id.example.com/api/agent/authenticate \
-H "Content-Type: application/json" \
-d "{
\"agent_id\": \"agent+deploy@example.com\",
\"challenge\": \"$CHALLENGE\",
\"signature\": \"$SIGNATURE\"
}"
Response:
{
"token": "eyJhbGciOiJFZERTQSIs...",
"agent_id": "a1b2c3d4-...",
"email": "agent+deploy@example.com",
"name": "deploy-bot",
"expires_in": 3600
}
Save the token — this is the agent's identity token (AuthN-JWT) for subsequent API calls.
Step 4: Request a Grant
With the agent authenticated, request permission to perform an action.
AGENT_TOKEN="eyJhbGciOiJFZERTQSIs..."
curl -X POST https://id.example.com/api/grants \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"requester": "agent+deploy@example.com",
"target_host": "prod-server.example.com",
"audience": "escapes",
"grant_type": "once",
"command": ["systemctl", "restart", "nginx"],
"reason": "Deploy hotfix #42"
}'
Response:
{
"id": "grant-uuid-...",
"type": "command",
"request": {
"requester": "agent+deploy@example.com",
"target_host": "prod-server.example.com",
"audience": "escapes",
"grant_type": "once",
"command": ["systemctl", "restart", "nginx"],
"cmd_hash": "sha256:a1b2c3...",
"reason": "Deploy hotfix #42"
},
"status": "pending",
"created_at": 1711234567890
}
Grant Types
| Type | Behavior | AuthZ-JWT Lifetime | Reusable? |
|---|---|---|---|
once | Single use — consumed after first use | 5 minutes | No |
timed | Valid for a time window | Until expires_at | Yes |
always | Standing permission until revoked | 1 hour (renewable) | Yes |
For timed grants, include a duration field (in seconds):
{
"grant_type": "timed",
"duration": 3600
}
Step 5: Human Approves the Grant
The grant is now pending. A human must approve it. This can happen through:
- Web UI — The owner/approver visits
/grant-approval?id=grant-uuid-...on the IdP - grapes CLI —
grapes approve grant-uuid-... - API — Using the Management Token or an authenticated session:
curl -X POST https://id.example.com/api/grants/grant-uuid-.../approve \
-H "Authorization: Bearer my-secret-management-token"
Response:
{
"grant": {
"id": "grant-uuid-...",
"status": "approved",
"decided_by": "admin@example.com",
"decided_at": 1711234568000,
"expires_at": 1711234868000
},
"authz_jwt": "eyJhbGciOiJFZERTQSIs..."
}
authz_jwt. You can also fetch it separately via /api/grants/:id/token.Step 6: Agent Retrieves the AuthZ-JWT
If the agent is polling for approval (rather than receiving the JWT from the approve response), it can fetch the token:
# Poll grant status until approved
curl https://id.example.com/api/grants/grant-uuid-... \
-H "Authorization: Bearer $AGENT_TOKEN"
# Once approved, get the AuthZ-JWT
curl -X POST https://id.example.com/api/grants/grant-uuid-.../token \
-H "Authorization: Bearer $AGENT_TOKEN"
Response:
{
"authz_jwt": "eyJhbGciOiJFZERTQSIs...",
"grant": { "id": "grant-uuid-...", "status": "approved" }
}
AuthZ-JWT Claims
The AuthZ-JWT contains everything the target system needs to validate the action:
{
"iss": "https://id.example.com",
"sub": "agent+deploy@example.com",
"aud": "escapes",
"target_host": "prod-server.example.com",
"grant_id": "grant-uuid-...",
"grant_type": "once",
"permissions": [],
"command": ["systemctl", "restart", "nginx"],
"cmd_hash": "sha256:a1b2c3...",
"decided_by": "admin@example.com",
"iat": 1711234568,
"exp": 1711234868,
"jti": "unique-token-id"
}
Step 7: Use the Grant
With escapes (privilege elevation)
Pass the AuthZ-JWT to escapes for local command execution:
AUTHZ_JWT="eyJhbGciOiJFZERTQSIs..."
escapes --grant "$AUTHZ_JWT" -- systemctl restart nginx
escapes verifies the JWT (issuer, signature, audience, target_host, cmd_hash), then executes the command as root.
With any target system
Any system can verify the AuthZ-JWT:
curl -X POST https://id.example.com/api/grants/verify \
-H "Content-Type: application/json" \
-d "{\"token\": \"$AUTHZ_JWT\"}"
Response:
{
"valid": true,
"claims": {
"sub": "agent+deploy@example.com",
"aud": "escapes",
"target_host": "prod-server.example.com",
"grant_type": "once",
"cmd_hash": "sha256:a1b2c3...",
"decided_by": "admin@example.com"
}
}
For once grants, the target should also consume the grant:
curl -X POST https://id.example.com/api/grants/grant-uuid-.../consume \
-H "Authorization: Bearer $AUTHZ_JWT"
Using grapes CLI Instead
The grapes CLI simplifies the entire flow into a few commands.
Login
# Human login (opens browser for passkey auth)
grapes login --idp https://id.example.com
# Agent login (key-based)
grapes login --idp https://id.example.com --key ~/.ssh/agent_key --email agent+deploy@example.com
Request and Execute in One Step
# Request grant, wait for approval, execute via escapes
grapes run escapes "systemctl restart nginx" --reason "Deploy hotfix #42"
# Request with timed approval
grapes run escapes "apt-get upgrade" --approval timed --duration 1h --reason "Security update"
Manual Flow
# Request a grant
grapes request "systemctl restart nginx" --audience escapes --wait
# Check status
grapes status grant-uuid-...
# List pending grants
grapes list --status pending
# Get the token
grapes token grant-uuid-...
Approve (as the approver)
grapes approve grant-uuid-...
grapes deny grant-uuid-...
Delegations
# Allow another user to act on your behalf
grapes delegate --to alice@example.com --at escapes --approval always
# List active delegations
grapes delegations