---
title: "Void an envelope"
method: POST
path: "/envelopes/{envelopeId}/void"
tags: ["Envelopes"]
---

# Void an envelope

`POST /envelopes/{envelopeId}/void`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/envelopes/{envelopeId}/void`
- Auth: HTTP bearer

Withdraws an envelope that is out for signature. The only allowed source state is `pending`, and only while no signer has signed yet.

Refusals both answer **400 `invalid_status`**: "Only pending envelopes can be voided" for any other state (`draft`, `sending`, `completing`, `completed`, `declined`, `voided`, `expired`), and "Cannot void an envelope after a signer has already signed" when at least one signer already signed. The status is re-asserted in the update predicate, so two callers racing the same envelope cannot both believe they voided it: the loser gets the same 400.

Voiding writes an `envelope.voided` audit row, fires the `envelope.voided` webhook once, and starts the shortened retention clock, since a voided envelope was never executed and only its audit trail is kept. It does **not** refund the credit that was spent on the send.

No request body. Honors `Idempotency-Key`.

## Headers

- `Idempotency-Key` string

## Response `200`

The envelope is now `voided`.

- object
  - `data` EnvelopeStateRef, required: An envelope named by public id together with the status it now holds.
    - `id` string, required
    - `status` 'draft' | 'sending' | 'pending' | 'completing' | 'completed' | 'declined' | 'voided' | 'expired', required: Where the envelope is in its lifecycle. `sending` and `completing` are transient; `completed`, `declined`, `voided`, and `expired` are terminal.

## Other responses

- `400`: `invalid_status`: the envelope is not `pending`, or a signer has already signed. Also the malformed-body and malformed-`Idempotency-Key` cases.
- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `409`: The resource's state forbids this transition, or another request owns the work. Codes: `invalid_state`, `invalid_status`, `credential_state`, `limit_exceeded`, `envelope_missing`, `not_retryable`, `owner_unavailable`, `workspace_unavailable`, `detection_not_completed`, `document_in_use`, `version_conflict`, `document_processing`, `document_processing_failed`, `normalized_pdf_unavailable`, `pages_not_rendered`, `document_not_ready`, `idempotency_in_progress` (which also carries `Retry-After`), `payment_replayed` (a USDC payment signature presented twice), `payment_pending` (a USDC settlement whose outcome is not yet known).
- `422`: The request parsed but its content is not acceptable. Codes: `validation_error` (envelope configuration, with an `issues` array in `details`), `invalid_envelope`, `envelope_not_ready`, `document_page_limit`, `idempotency_key_reuse`.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
