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.
https://shonin.dev/api/v1All 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.
curl -H "Authorization: Bearer sk_your_api_key" \
https://shonin.dev/api/v1/approvalsCreate Approval
POST/v1/approvalsCreates 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
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | required | A short description of what needs approval. Shown prominently in the email. |
| approver_email | string | required | The email address of the person who will approve or reject. |
| context | string | optional | Optional extra context displayed in the email below the action. |
| webhook_url | string | optional | URL to POST the decision to when the approver clicks Approve or Reject. |
| expires_in_hours | number | optional | How many hours before the approval link expires. Defaults to 24. |
Example request
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
{
"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/:idReturns 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
curl https://shonin.dev/api/v1/approvals/a1b2c3d4 \
-H "Authorization: Bearer sk_your_api_key"Example response 200 OK
{
"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/:tokenRecords 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.
approve_token and a reject_token. The appropriate button in the email links to the matching token URL. Tokens are single-use.Path parameter
| Name | Type | Required | Description |
|---|---|---|---|
| token | string | required | The approve_token or reject_token from the approval object. Determines the decision recorded. |
Behavior
Webhook payload
If webhook_url was set on the approval, Shonin will POST the following JSON immediately after the decision is recorded.
{
"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.
{ "error": "Invalid API key" }Error codes
| Status | Meaning | Common cause |
|---|---|---|
| 401 | Invalid API key | Missing or incorrect Authorization header. |
| 400 | Validation error | Required field missing or value is the wrong type. |
| 404 | Not found | Approval ID does not exist or belongs to a different account. |
| 410 | Expired | The approval link has passed its expiry time. |
| 409 | Already decided | An approve or reject action has already been recorded. |