Approve an inbound request
Approves the draft envelope an inbound email produced, through the same executor as the approval link in the confirmation mail and the reply keyword.
This is an agent send, not a review. The link and the keyword are a person approving the draft they were shown. A credential calling this endpoint is sending on its own authority, so it answers to every agent guardrail a direct sendEnvelope does: it is refused while the workspace requires send approval or while the draft has an open send approval, refused when any signer or CC is on the deny list, counted against this credential's own spend limits, and recorded as sent by this credential.
What it actually creates: nothing new. The draft already exists (the mail agent converted the chosen attachment into a document and built the envelope from the parsed intent before parking the request). Approval claims that draft with a compare-and-set, hands it to the same send worker POST /envelopes/{envelopeId}/send uses, and moves the conversation to approved through the state machine so its audit row and lifecycle webhook fire exactly once.
Asynchronous, like a direct send. The 202 means the draft was claimed and queued; the worker owns the single credit charge and the delivery. Poll GET /envelopes/{envelopeId} on the returned envelope.id, or subscribe to envelope.sent / envelope.send_failed / envelope.completed.
Preconditions. awaiting_approval is the only actionable state. Any other state answers 400 invalid_status with details.state. Note the split: this endpoint uses 400 invalid_status where the archive and reminder endpoints use 409 invalid_state for a comparable refusal.
Webhooks. Reaching approved fires email_envelope.approved. email_envelope.rejected is its counterpart for a conversation the agent rejected (a refused or unusable message), not for a cancellation: POST .../cancel moves the request to cancelled and fires neither.
Two concurrent approvals are safe without an Idempotency-Key: one claims the draft and the other is told it has already moved. With a key, the retry replays the first response verbatim.
No request body. Metered in the send rate bucket, and costs envelopes:send for the same reason a direct send does.
Request
- Base URL: https://inkseal.net/api/v1
- URL: https://inkseal.net/api/v1/inbound-requests/{requestId}/approve
- Auth: HTTP bearer
Headers
Makes this mutation retry-safe. 1 to 255 printable ASCII characters (0x20 to 0x7e), trimmed before use. The key is scoped to the workspace and the credential that sent it. Reusing it with a different method, path, or payload answers 422 idempotency_key_reuse. A completed attempt replays its stored status and body with an Idempotent-Replay: true response header; 5xx, 429, and non-JSON answers leave the key retryable. A concurrent request holding the 90 second lease answers 409 idempotency_in_progress. Records are retained 24 hours.
Response
The draft was claimed and queued. The conversation is re-read after the transition, so state is approved and envelope.id names the envelope now sending.
Example response
{
"data": {
"id": "Ib5qT8nW3vK7xR2mB9sYd",
"state": "awaiting_approval",
"error": null,
"intent": {
"signers": [
{
"name": "Dana Reyes",
"email": "dana@acme.test",
"order": 1
}
],
"signingOrder": "sequential",
"subject": "Master Services Agreement",
"message": null,
"expiresInDays": 30,
"attachmentIndex": 0,
"notes": null
},
"missingInformation": [],
"attachments": [
{
"filename": "msa.pdf",
"contentType": "application/pdf",
"size": 184213,
"status": "stored",
"refusedReason": null
}
],
"document": {
"id": "Ku7bV2xQpL9mNr4sT1yZa"
},
"envelope": {
"id": "Jd3kR8sVqW2nX5cY7bLmP"
},
"clarificationRoundTrips": 0,
"createdAt": "2026-08-21T08:55:00.000Z",
"updatedAt": "2026-08-21T08:56:12.000Z"
}
}Changes
No recorded changes to this operation across all 1 revision of this API.