Shoninshonin

Overview

Shonin is a human-in-the-loop approval API. Send an approval request to any email address and wait for a human decision before your automation continues. No account required for approvers — they just click a link.

Base URLhttps://shonin.dev/api/v1

All requests must include an Authorization: Bearer <api_key> header, except the GET /v1/decide/:token endpoint which is public and used directly by approvers clicking email links.

Authentication

Pass your API key as a Bearer token in the Authorization header on every request.

bash
curl -H "Authorization: Bearer sk_your_api_key" \
  https://shonin.dev/api/v1/approvals
Keep your API key secret. Do not expose it in client-side code or public repositories.

Create Approval

POST/v1/approvals

Creates a new approval request and sends an email to the approver with Approve and Reject buttons. Returns immediately — the approval will be in pending status until the approver decides.

Request body

NameTypeRequiredDescription
actionstringrequiredA short description of what needs approval. Shown prominently in the email.
approver_emailstringrequiredThe email address of the person who will approve or reject.
contextstringoptionalOptional extra context displayed in the email below the action.
webhook_urlstringoptionalURL to POST the decision to when the approver clicks Approve or Reject.
expires_in_hoursnumberoptionalHow many hours before the approval link expires. Defaults to 24.

Example request

bash
curl -X POST https://shonin.dev/api/v1/approvals \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "Deploy to production",
    "approver_email": "cto@company.com",
    "context": "PR #247 merged, 3 files changed",
    "webhook_url": "https://yourapp.com/webhooks/shonin"
  }'

Example response 201 Created

json
{
  "id": "a1b2c3d4-...",
  "status": "pending",
  "approve_token": "Abc123...",
  "reject_token": "Xyz789...",
  "created_at": "2026-03-21T18:00:00Z",
  "expires_at": "2026-03-22T18:00:00Z"
}

Get Approval

GET/v1/approvals/:id

Returns the current state of an approval. Use this to poll for a decision if you are not using webhooks. Only approvals belonging to the authenticated account are returned.

Example request

bash
curl https://shonin.dev/api/v1/approvals/a1b2c3d4 \
  -H "Authorization: Bearer sk_your_api_key"

Example response 200 OK

json
{
  "id": "a1b2c3d4-...",
  "account_id": "sk_your_api_key",
  "action": "Deploy to production",
  "approver_email": "cto@company.com",
  "context": "PR #247 merged, 3 files changed",
  "status": "approved",
  "webhook_url": "https://yourapp.com/webhooks/shonin",
  "expires_at": "2026-03-22T18:00:00Z",
  "decided_at": "2026-03-21T18:45:00Z",
  "created_at": "2026-03-21T18:00:00Z"
}

Decide

GET/v1/decide/:token

Records the approver's decision and returns an HTML confirmation page. This endpoint requires no authentication — it is the URL embedded in the approval email that the approver clicks directly.

Each approval has two tokens: an approve_token and a reject_token. The appropriate button in the email links to the matching token URL. Tokens are single-use.

Path parameter

NameTypeRequiredDescription
tokenstringrequiredThe approve_token or reject_token from the approval object. Determines the decision recorded.

Behavior

Token matches approve_tokenRecords status = approved, fires webhook if set, returns HTML success page.
Token matches reject_tokenRecords status = rejected, fires webhook if set, returns HTML rejection page.
Approval already decidedReturns an HTML page indicating it was already acted on. No change to state.
Approval expiredReturns an HTML expired page. No change to state.
Token not foundReturns an HTML invalid link page.

Webhook payload

If webhook_url was set on the approval, Shonin will POST the following JSON immediately after the decision is recorded.

json
{
  "id": "a1b2c3d4-...",
  "status": "approved",
  "decided_at": "2026-03-21T18:45:00Z"
}

Errors

All errors return a JSON object with an error field containing a human-readable message.

json
{ "error": "Invalid API key" }

Error codes

StatusMeaningCommon cause
401Invalid API keyMissing or incorrect Authorization header.
400Validation errorRequired field missing or value is the wrong type.
404Not foundApproval ID does not exist or belongs to a different account.
410ExpiredThe approval link has passed its expiry time.
409Already decidedAn approve or reject action has already been recorded.