API Documentation

Simple REST API for secure, single-use approval links.

Quick Start

  1. 1. Sign in with GitHub to create a project
  2. 2. Generate an API key from your dashboard
  3. 3. Create an approval token with your desired actions
  4. 4. Share the link (email, chat, SMS)
  5. 5. Poll for the result in your automation

Security Model

Ottr uses magic-link security - the same model used by password reset emails. Security comes from the URL being cryptographically unguessable, not from a login session.

128-bit URLs ULIDs with 128 bits of entropy. Unguessable.
CSRF tokens Must load approval page first - bots can't POST directly.
single-use Each link works once. Can't replay.
auto-expiry Configurable TTL (default: 10 minutes).
rate limiting IP-based limits prevent brute-force.
optional PIN Locks after 5 failed attempts.

Try It Now

Replace YOUR_API_KEY with your write token from the dashboard.

1. Create an approval link

curl -X POST https://api.ottr.run/v1/approvals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key_path": "demo/test", "value": "approved"}'

2. Check the value

curl https://api.ottr.run/v1/key/demo/test \
  -H "Authorization: Bearer YOUR_API_KEY"

3. Open the approval URL and click the button. Check again - value changes.

Authentication

All API requests require Bearer token authentication:

Authorization: Bearer YOUR_API_KEY
otw_* write key — read, write, create approvals
otr_* read key — read values only

Key/Value Storage

GET /v1/key/{key_path}

Get the current value of a key.

curl https://api.ottr.run/v1/key/deploy/prod \
  -H "Authorization: Bearer YOUR_API_KEY"
GET /v1/key/{key_path}/pending

Check if there's a pending approval for a key.

{"pending": {"approval_id": "...", "expires_at": "..."}}
PUT /v1/key/{key_path}

Set a key value directly. Requires write key.

curl -X PUT https://api.ottr.run/v1/key/deploy/prod \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d 'deployed'

Approvals API

POST /v1/approvals

Create a new approval token.

key_path key to update when token is used
value value to set (simple format)
actions array of 1-3 actions (full format)
ttl_seconds time to live (default: 600)
initial_value set key to this before token creation
info context shown on approval page
ref_id reference ID for tracking
curl -X POST https://api.ottr.run/v1/approvals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key_path": "deploy/prod",
    "actions": [
      {"key": "approve", "value": "approved", "style": "primary"},
      {"key": "reject", "value": "rejected", "style": "danger"}
    ],
    "info": "Deploy v2.1.0 to production"
  }'

Response:

{
  "approval_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "url": "https://ottr.run/a/01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "expires_at": "2025-01-01T12:30:00Z"
}
POST /v1/approvals/{id}/invalidate

Invalidate an approval before it expires. Returns 204.

Button Styles

Customize action buttons with preset styles or hex colors:

primary secondary danger warning info default

Or use hex colors like #ff6b6b

Approval Status

pending active, waiting to be used
executed successfully used
expired TTL passed without use
invalidated manually invalidated
locked 5 failed PIN attempts

Errors

All errors return JSON with an error field:

{"error": "invalid action key"}
400 bad request
401 unauthorized
403 forbidden (expired or used)
404 not found
429 rate limited or locked

Rate Limits

public

demo: 10/min

approval read: 60/min

approval action: 30/min

dashboard

read: 300/min

write: 60/min

automation (API key)

key read: 600/min

key write: 120/min

approval create: 60/min

Response headers:

X-RateLimit-Limit — max requests

X-RateLimit-Remaining — remaining

X-RateLimit-Reset — seconds until reset

Retry-After — seconds to wait (on 429)

Ready to get started?