---
title: "Request approval of an inbound sender"
method: POST
path: "/account/authorized-senders"
tags: ["Account"]
---

# Request approval of an inbound sender

`POST /account/authorized-senders`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/account/authorized-senders`
- Auth: HTTP bearer

Asks for an address to be added to the workspace's authorized senders.

**A credential cannot authorize an address, and this call mails nothing.** It records a request: the grant is created `pending` with `awaitingApproval: true`, and no confirmation link exists yet. A workspace owner or admin approves it from the InkSeal dashboard, which mails a single-use link to the address; only opening that link from the mailbox activates the grant. Approval proves the workspace wants the address and the link proves whoever named it controls the mailbox, so neither alone is enough and controlling the mailbox never makes an address a sender. Report the pending request to your human and poll `listAuthorizedSenders` to see when it is active.

Re-adding a `revoked` address records a fresh request. Re-adding a `pending` one returns it unchanged, so a link a person already sent keeps working. An address that is already `active` answers `409` `invalid_state` rather than being reset, because that would drop a working grant back to `pending` and silently stop mail that works today. The workspace owner's own address answers `409` `invalid_state` too: it is authorized by construction.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `email` string, email, required: The address to authorize. Trimmed, and matched case-insensitively against the addresses already listed.
  - `kind` 'alias' | 'delegate': A label for the humans reading the list: `alias` is another address of someone already in the workspace, `delegate` is somebody else acting for it. Trust is identical either way.

## Response `201`

The grant, `pending` and awaiting a workspace person's approval. `confirmationEmailSent` is always false here.

- object
  - `data` object, required
    - `sender` AuthorizedSender, required: One address the mail agent will accept documents from. The grant belongs to the workspace, and it authorizes nothing until a workspace person has approved it and the mailbox it names has confirmed it.
      - `id` string, required: The grant's public identifier.
      - `email` string, email, required: The address mail must come from. Matching is case-insensitive, and one address can hold only one grant per workspace.
      - `kind` 'owner' | 'alias' | 'delegate', required: `owner` is the workspace owner's own address, authorized by construction and neither enrollable nor revocable here. It authorizes only while it is still the owner's address; one left by a former owner or an old address authorizes nothing and can be revoked. `alias` is another address of someone already in the workspace, `delegate` is somebody else acting for it; the two carry identical trust and differ only as a label for the humans reading the list.
      - `status` 'pending' | 'active' | 'revoked', required: `pending`: not yet confirmed, so mail from this address is still refused. Either a confirmation link was mailed and has not been used, or, when `awaitingApproval` is true, no link exists yet. `active`: confirmed, and mail from it is processed. `revoked`: it was trusted once and is not now.
      - `confirmationSentAt` string, date-time, nullable, required: When the most recent confirmation link was mailed.
      - `expiresAt` string, date-time, nullable, required: While `pending`, the deadline for using the confirmation link. Cleared on confirmation, so an active grant ordinarily carries none.
      - `lastUsedAt` string, date-time, nullable, required: When a message from this address was last accepted, or null if never.
      - `awaitingApproval` boolean, required: True when this non-owner grant has no recorded workspace person approving it. No confirmation can activate it until an owner or admin approves in the dashboard. Legacy active rows lacking an approver also authorize nothing and require review.
      - `createdAt` string, date-time, required
      - `updatedAt` string, date-time, required
    - `confirmationEmailSent` boolean, required: Whether a confirmation email went out. Always false from this operation for a credential: nothing is mailed until a workspace person approves the request.

## Other responses

- `400`: The request is malformed or the resource is in the wrong state for a synchronous check. Codes: `validation_error`, `invalid_body`, `invalid_parameter`, `invalid_idempotency_key`, `invalid_event_id`, `invalid_file`, `invalid_url`, `invalid_status`, `key_mismatch`, `no_signers`, `not_completed`, `envelope_exists`, `document_not_ready`, `upload_failed`.
- `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).
- `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)
