Sendabrief logoSendabrief

API Documentation

The Sendabrief REST API lets you manage SOPs programmatically, create, read, update, and delete SOPs from any language or tool. It's available on the Sendabrief Pro plan.

You need an API key to use this API. Generate one in your settings →

Authentication

Every request must include an Authorization header with a Bearer token:

Authorization: Bearer sb_your_api_key_here

API keys are scoped to a workspace and carry Sendabrief Pro permissions. Keys are shown only once when created, store them securely (e.g. an environment variable). You can revoke a key at any time from Settings → API.

Base URL

https://sendabrief.com/api/v1

All endpoints are relative to this base URL.

Rate limiting

Each API key is limited to 100 requests per 60-second window (sliding). Every response includes the following headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window (100)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (ms) when the window resets

When the limit is exceeded, the API returns 429 Too Many Requests with error code RATE_LIMITED.

CORS

All API endpoints support cross-origin requests from any origin. The API returns the following CORS headers:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type

Preflight OPTIONS requests are handled automatically on all routes.

Step object

Each entry in a SOP's steps array accepts the following fields. Only text is required, everything else is optional and passed through as-is when present, this is the same step shape the dashboard editor writes.

FieldTypeDescription
idstringStable identity for the step. Generated for you if omitted - required if a decision step's branch needs to target this step.
textstringRequired. Plain-text instruction for this step.
typestring"step" (default), "decision", or "end".
richTextstringOptional rich-text HTML rendered instead of text when present.
branchesarrayOnly meaningful when type is "decision": [{ id, label, targetStepId }]. targetStepId: null ends the procedure on that answer.
slastringFree-text timing expectation, e.g. "Within 24 hours".
screenshotUrlstringAnnotated screenshot URL for this step.
annotationBoxobject{ x, y, width, height } in fractional (0-1) coordinates, paired with screenshotUrl.
narrationTranscriptstringVoice narration captured for this step during a screen recording.
sourceDocRefobject{ fileName, page?, section? } - which uploaded document this step came from.
extractedMediaUrlstringImage/diagram carried over from a source document.

order is always recomputed from the step's position in the array, it isn't settable directly.

Allowed input_type values

transcript, audio, chat, mixed, manual, document, screen_recording, text, meeting

Source document fields

When an uploaded document is already a written SOP, it is migrated rather than rewritten, and the sections a controlled procedure carries are preserved on these four fields. A SOP created any other way returns null for all of them (and an empty array for revision_history), never absent keys.

FieldTypeDescription
scopestringThe source document's Scope section, in its own words, including any stated exclusions. Null for a SOP that was generated rather than migrated.
reference_docsstringThe source's References or Related Documents section. Null when it had none.
source_sectionsarraySupporting sections from the source document that govern the whole procedure rather than one step, as {title, body}, in source order. Empty for generated SOPs.
revision_historyarrayThe source's revision table, transcribed: [{ version?, date?, author?, summary? }]. Dates are kept exactly as the document wrote them and are never normalised. Empty array when the document had no such table.
document_controlobject{ documentNumber?, version?, effectiveDate?, approver?, owner?, reviewCadence? } from the document's control header. Null when absent.

These are read-only. They record what the uploaded file contained, so POST and PATCH ignore them. The document's own Purpose section is carried into description rather than a field of its own.

Webhooks

Subscribe to SOP lifecycle events using the endpoints below, or from Settings → Webhooks. Each event fires regardless of whether the change came from the dashboard or the API.

Event catalog

EventDescription
sop.createdSOP created
sop.updatedSOP updated
sop.deletedSOP deleted
sop.viewedSOP viewed
sop.acknowledgedSOP acknowledged (Mark as read)

Payload

{
  "id": "uuid",
  "event": "sop.created",
  "created_at": "2024-01-01T00:00:00Z",
  "data": { /* the resource, same shape as the REST response */ }
}

Verifying the signature

Every delivery includes an X-Sendabrief-Signature header:sha256=<hex digest>, an HMAC-SHA256 of the raw request body using the webhook's signing secret (returned once, at creation time). Recompute the HMAC over the exact bytes you received and compare, don't re-serialize the parsed JSON first.

Failed deliveries are retried once. Delivery history (status code, success/failure) is visible from Settings → Webhooks.

Errors

All errors follow a consistent JSON shape:

{
  "error": "Human-readable message",
  "code":  "MACHINE_READABLE_CODE"
}

Use the code field for programmatic error handling:

CodeStatusMeaning
MISSING_AUTH401No Authorization header was sent.
INVALID_KEY_FORMAT401Key does not start with sb_ or is the wrong length.
INVALID_API_KEY401Key not found in database or has been revoked.
KEY_REVOKED401Key exists but was explicitly revoked.
PLAN_REQUIRED403The workspace is not on the Sendabrief Pro plan.
RATE_LIMITED429More than 100 requests in the last 60 seconds.
NOT_FOUND404The requested resource does not exist in this workspace.
VALIDATION_ERROR400The request body failed validation.
REVIEW_REQUIRED409The SOP has require_review set and can't be updated directly.
INTERNAL_ERROR500Unexpected server error.
GET/sops

List SOPs

Returns a paginated list of all SOPs in your workspace, ordered by last-updated descending.

Query parameters

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoResults per page, 1–100 (default: 20)

Response

{
  "data": [
    {
      "id": "uuid",
      "title": "Onboard a new client",
      "trigger": "When a contract is signed",
      "steps": [
        { "id": "step-uuid", "order": 1, "type": "step", "text": "Send welcome email" },
        { "id": "step-uuid-2", "order": 2, "type": "end", "text": "Client onboarded" }
      ],
      "roles": ["Account Manager"],
      "description": null,
      "definitions": [],
      "input_type": "transcript",
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z",
      "verified_at": "2024-01-01T00:00:00Z",
      "share_token": "shr_abc123",
      "content_version": 1,
      "unattached_media": [],
      "folder_id": null,
      "review_cadence": "none",
      "assigned_verifier_id": null,
      "review_due_at": null,
      "require_review": false,
      "scope": null,
      "reference_docs": null,
      "source_sections": [],
      "revision_history": [],
      "document_control": null,
      "tags": [{ "id": "uuid", "name": "Onboarding" }]
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "totalPages": 3
  }
}

Error codes

StatusCodeReason
401MISSING_AUTHNo Authorization header provided
401INVALID_API_KEYKey not found or revoked
403PLAN_REQUIREDWorkspace is not on the Sendabrief Pro plan
429RATE_LIMITED100 req/min exceeded

Example

curl -X GET "https://sendabrief.com/api/v1/sops" \
  -H "Authorization: Bearer sb_your_key_here"
POST/sops

Create a SOP

Creates a new SOP from structured JSON. Does not use AI generation, steps are stored exactly as provided. Goes through the same create path as the dashboard, so it's versioned and counted the same way.

Request body

{
  "title": "Onboard a new client",          // required
  "steps": [                                 // required, non-empty - see "Step object" below
    { "text": "Send welcome email" },
    { "text": "Schedule kickoff call" }
  ],
  "trigger": "When a contract is signed",   // optional
  "roles": ["Account Manager"],             // optional
  "description": "Free-text description",   // optional
  "definitions": [],                        // optional, glossary entries: [{ "term", "definition" }]
  "input_type": "transcript"                // optional, default "transcript" - see allowed values below
}

Response

{
  "data": {
    "id": "uuid",
    "title": "Onboard a new client",
    "trigger": "When a contract is signed",
    "steps": [
      { "id": "step-uuid", "order": 1, "type": "step", "text": "Send welcome email" },
      { "id": "step-uuid-2", "order": 2, "type": "end", "text": "Client onboarded" }
    ],
    "roles": ["Account Manager"],
    "description": null,
    "definitions": [],
    "input_type": "transcript",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z",
    "verified_at": "2024-01-01T00:00:00Z",
    "share_token": "shr_abc123",
    "content_version": 1,
    "unattached_media": [],
    "folder_id": null,
    "review_cadence": "none",
    "assigned_verifier_id": null,
    "review_due_at": null,
    "require_review": false,
    "scope": null,
    "reference_docs": null,
    "source_sections": [],
    "revision_history": [],
    "document_control": null,
    "tags": [{ "id": "uuid", "name": "Onboarding" }]
  }
}

Error codes

StatusCodeReason
400VALIDATION_ERRORtitle missing, steps empty/invalid, or input_type not recognized
401INVALID_API_KEYKey not found or revoked
403PLAN_REQUIREDSendabrief Pro plan required
429RATE_LIMITED100 req/min exceeded

Example

curl -X POST "https://sendabrief.com/api/v1/sops" \
  -H "Authorization: Bearer sb_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title":"My SOP","steps":[{"text":"First step"}]}'
GET/sops/:id

Get a SOP

Fetches a single SOP by its UUID. The SOP must belong to the authenticated workspace.

Response

{
  "data": {
    "id": "uuid",
    "title": "Onboard a new client",
    "trigger": "When a contract is signed",
    "steps": [
      { "id": "step-uuid", "order": 1, "type": "step", "text": "Send welcome email" },
      { "id": "step-uuid-2", "order": 2, "type": "end", "text": "Client onboarded" }
    ],
    "roles": ["Account Manager"],
    "description": null,
    "definitions": [],
    "input_type": "transcript",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z",
    "verified_at": "2024-01-01T00:00:00Z",
    "share_token": "shr_abc123",
    "content_version": 1,
    "unattached_media": [],
    "folder_id": null,
    "review_cadence": "none",
    "assigned_verifier_id": null,
    "review_due_at": null,
    "require_review": false,
    "scope": null,
    "reference_docs": null,
    "source_sections": [],
    "revision_history": [],
    "document_control": null,
    "tags": [{ "id": "uuid", "name": "Onboarding" }]
  }
}

Error codes

StatusCodeReason
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDSOP does not exist or belongs to a different workspace

Example

curl -X GET "https://sendabrief.com/api/v1/sops/<id>" \
  -H "Authorization: Bearer sb_your_key_here"
PATCH/sops/:id

Update a SOP

Partially updates a SOP. Only the fields you include are changed, omit any field to leave it unchanged. Snapshots the current content into version history before applying, same as an edit made from the dashboard.

Request body

{
  "title": "Updated title",           // optional
  "trigger": "New trigger text",      // optional, pass null to clear
  "steps": [                          // optional, replaces all steps - see "Step object" below
    { "text": "Updated step one" }
  ],
  "roles": ["Manager"],               // optional, replaces roles array
  "description": "Updated description", // optional, pass null to clear
  "definitions": [],                  // optional, replaces definitions array
  "input_type": "meeting"             // optional - see allowed values below
}

Response

{
  "data": {
    "id": "uuid",
    "title": "Onboard a new client",
    "trigger": "When a contract is signed",
    "steps": [
      { "id": "step-uuid", "order": 1, "type": "step", "text": "Send welcome email" },
      { "id": "step-uuid-2", "order": 2, "type": "end", "text": "Client onboarded" }
    ],
    "roles": ["Account Manager"],
    "description": null,
    "definitions": [],
    "input_type": "transcript",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z",
    "verified_at": "2024-01-01T00:00:00Z",
    "share_token": "shr_abc123",
    "content_version": 1,
    "unattached_media": [],
    "folder_id": null,
    "review_cadence": "none",
    "assigned_verifier_id": null,
    "review_due_at": null,
    "require_review": false,
    "scope": null,
    "reference_docs": null,
    "source_sections": [],
    "revision_history": [],
    "document_control": null,
    "tags": [{ "id": "uuid", "name": "Onboarding" }]
  }
}

Error codes

StatusCodeReason
400VALIDATION_ERRORRequest body empty, or a provided field is invalid
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDSOP does not exist in this workspace
409REVIEW_REQUIREDSOP has require_review set - update it from the dashboard instead

Example

curl -X PATCH "https://sendabrief.com/api/v1/sops/<id>" \
  -H "Authorization: Bearer sb_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title":"Updated title"}'
DELETE/sops/:id

Delete a SOP

Permanently deletes a SOP. This action cannot be undone.

Response

{
  "deleted": true,
  "id": "uuid"
}

Error codes

StatusCodeReason
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDSOP does not exist in this workspace

Example

curl -X DELETE "https://sendabrief.com/api/v1/sops/<id>" \
  -H "Authorization: Bearer sb_your_key_here"
GET/webhooks

List webhooks

Returns every webhook endpoint registered in your workspace. The signing secret is never included here, only at creation time.

Response

{
  "data": [
    {
      "id": "uuid",
      "name": "Zapier",
      "url": "https://hooks.zapier.com/...",
      "events": ["sop.created", "sop.updated"],
      "enabled": true,
      "created_at": "2024-01-01T00:00:00Z",
      "last_triggered_at": "2024-01-02T00:00:00Z"
    }
  ]
}

Error codes

StatusCodeReason
401INVALID_API_KEYKey not found or revoked
403PLAN_REQUIREDWorkspace is not on the Sendabrief Pro plan

Example

curl -X GET "https://sendabrief.com/api/v1/webhooks" \
  -H "Authorization: Bearer sb_your_key_here"
POST/webhooks

Register a webhook

Registers a new webhook endpoint. This is what lets an automation platform (Zapier, Make, your own service) subscribe itself instead of a human clicking through the dashboard - see the Webhooks section below for the event catalog and signature verification.

Request body

{
  "url": "https://your-endpoint.example.com/hooks",  // required (Zapier's "targetUrl" also accepted)
  "events": ["sop.created", "sop.updated"],           // required, at least one - see event catalog below
  "name": "My integration"                            // optional, defaults to "API-registered webhook"
}

Response

{
  "data": {
    "id": "uuid",
    "name": "My integration",
    "url": "https://your-endpoint.example.com/hooks",
    "events": ["sop.created"],
    "enabled": true,
    "created_at": "2024-01-01T00:00:00Z",
    "last_triggered_at": null,
    "secret": "whsec_..."
  }
}

Error codes

StatusCodeReason
400VALIDATION_ERRORurl missing, events empty/unrecognized, or the URL fails validation (e.g. points at a private/internal address)
401INVALID_API_KEYKey not found or revoked
403PLAN_REQUIREDSendabrief Pro plan required

Example

curl -X POST "https://sendabrief.com/api/v1/webhooks" \
  -H "Authorization: Bearer sb_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-endpoint.example.com/hooks","events":["sop.created"]}'
GET/webhooks/:id

Get a webhook

Fetches a single webhook by its UUID. Never includes the signing secret.

Response

{
  "data": {
    "id": "uuid",
    "name": "Zapier",
    "url": "https://hooks.zapier.com/...",
    "events": ["sop.created"],
    "enabled": true,
    "created_at": "2024-01-01T00:00:00Z",
    "last_triggered_at": null
  }
}

Error codes

StatusCodeReason
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDWebhook does not exist in this workspace

Example

curl -X GET "https://sendabrief.com/api/v1/webhooks/<id>" \
  -H "Authorization: Bearer sb_your_key_here"
PATCH/webhooks/:id

Update a webhook

Partially updates a webhook's name, URL, subscribed events, or enabled state.

Request body

{
  "name": "New name",                     // optional
  "url": "https://new-endpoint.example",  // optional, re-validated
  "events": ["sop.deleted"],              // optional, replaces the events array
  "enabled": false                        // optional, pause without deleting
}

Response

{
  "data": {
    "id": "uuid",
    "name": "New name",
    "url": "https://new-endpoint.example",
    "events": ["sop.deleted"],
    "enabled": false,
    "created_at": "2024-01-01T00:00:00Z",
    "last_triggered_at": null
  }
}

Error codes

StatusCodeReason
400VALIDATION_ERRORRequest body empty, or a provided field is invalid
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDWebhook does not exist in this workspace

Example

curl -X PATCH "https://sendabrief.com/api/v1/webhooks/<id>" \
  -H "Authorization: Bearer sb_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
DELETE/webhooks/:id

Delete a webhook

Unsubscribes and permanently deletes a webhook. This is the "unsubscribe" half of the Zapier REST Hook contract.

Response

{
  "deleted": true,
  "id": "uuid"
}

Error codes

StatusCodeReason
401INVALID_API_KEYKey not found or revoked
404NOT_FOUNDWebhook does not exist in this workspace

Example

curl -X DELETE "https://sendabrief.com/api/v1/webhooks/<id>" \
  -H "Authorization: Bearer sb_your_key_here"