Skip to content

Approval

An Approval is a Task to allow or refuse one pending Action that exceeds a Limit or a Mandate.

Properties

PropertyTypeRequiredDescription
verdictstringOne of allowed or refused.
deadlinedatetimeWhen an unanswered Approval cancels the Action.
Inherited from Task: 3 properties
PropertyTypeRequiredDescription
duedatetimeWhen the Task is due.
priorityintegerIts rank in the inbox, highest first.
recurrencestringThe schedule a recurring duty repeats on.
Inherited from Action: 11 properties
PropertyTypeRequiredDescription
statusstringrequiredOne of pending, done, failed, canceled, or refused.
reasonstringWhy a pending Action waits: queued, running, retrying, awaitingWorker, awaitingInput, awaitingSource, awaitingAction, or awaitingTime.
modestringrequiredHow the caller invoked it: try, send, or do.
byWorkerThe Worker that performed it.
forPartyThe Party it was done on behalf of.
viastringThe Interface or request it arrived through.
onstringThe Action or Event that caused it, such as the failed attempt a retry follows.
thingstringThe record, at the Version the Action was decided against.
inputjsonThe input it was called with.
createdAtdatetimeWhen it was called.
reservedmoneyThe most this Action may cost, held against its Limits from the check until it settles, billed misses included.

Object

JSON
{
  "$id": "https://api.sb/shiftnote.app/approvals/approval_1",
  "$type": "Approval",
  "status": "pending",
  "reason": "queued",
  "mode": "try",
  "by": "worker_1",
  "for": "party_1",
  "via": "API",
  "on": "Invoice.overdue",
  "thing": "invoice_1@v3",
  "input": {},
  "createdAt": "2026-10-22T12:00:00Z",
  "reserved": {
    "amount": 79,
    "currency": "USD"
  },
  "due": "2026-10-22T12:00:00Z",
  "priority": 1,
  "recurrence": "Weekly on Friday",
  "verdict": "allowed",
  "deadline": "2026-10-22T12:00:00Z"
}

Relationships

Lifecycle

Approval lifecycle: pending, approved (ends), refused (ends), expired (ends)pending → approved: Approval.approvepending → refused: Approval.refusepending → expired: on Approval.expiredpendingapprovedrefusedexpired
StateMeaningMoves to
pending (first)An Action waits on the answer.approved by Approval.approve; refused by Approval.refuse; expired on Approval.expired when nobody answered by its deadline
approvedThe Action it held runs.None: it ends here.
refusedThe Action it held ends refused, with the reason given.None: it ends here.
expiredNobody answered by the deadline, so the Action it held ends canceled.None: it ends here.

Verbs

  1. createCreates an Approval.
    POST /{namespace}/approvalsdoApproval.created
  2. getReturns one Approval by id.
    GET /{namespace}/approvals/{id}readNo Event
  3. listReturns Approvals, newest first, one page at a time.
    GET /{namespace}/approvalsreadNo Event
  4. updateChanges the given properties of an Approval.
    PATCH /{namespace}/approvals/{id}doApproval.updated
  5. deleteDeletes an Approval; its history is kept.
    DELETE /{namespace}/approvals/{id}doApproval.deleted
  6. approveApprove allows the pending Action an Approval holds, and that Action runs.
    POST /{namespace}/approvals/{approval}/approvedoApproval.approved
  7. refuseRefuse declines an Approval, which sets the Action it holds to `refused` with the reason given.
    POST /{namespace}/approvals/{approval}/refusedoApproval.refused

Events

Events no Verb emits. Each Verb’s Event is in the Verbs table above; every Event is delivered in the same shape.

Approval.expired

Nobody answered an Approval by its deadline, so the Action it held ends. Raised by a clock.

FieldTypeRequiredDescription
namestringrequiredApproval.expired.
atdatetimerequiredWhen it happened.
seqintegerIts position in the namespace, one more than the Event before; any seq is a cursor into the history.
datajsonrequiredThe Thing as the Action left it.
data.approvalApprovalrequiredThe Approval as the change left it.
data.approval.statusstringrequiredOne of pending, done, failed, canceled, or refused.
data.approval.reasonstringWhy a pending Action waits: queued, running, retrying, awaitingWorker, awaitingInput, awaitingSource, awaitingAction, or awaitingTime.
data.approval.modestringrequiredHow the caller invoked it: try, send, or do.
data.approval.byWorkerThe Worker that performed it.
data.approval.forPartyThe Party it was done on behalf of.
data.approval.viastringThe Interface or request it arrived through.
data.approval.onstringThe Action or Event that caused it, such as the failed attempt a retry follows.
data.approval.thingstringThe record, at the Version the Action was decided against.
data.approval.inputjsonThe input it was called with.
data.approval.createdAtdatetimeWhen it was called.
data.approval.reservedmoneyThe most this Action may cost, held against its Limits from the check until it settles, billed misses included.
data.approval.duedatetimeWhen the Task is due.
data.approval.priorityintegerIts rank in the inbox, highest first.
data.approval.recurrencestringThe schedule a recurring duty repeats on.
data.approval.verdictstringOne of allowed or refused.
data.approval.deadlinedatetimeWhen an unanswered Approval cancels the Action.
JSON
{
  "$id": "https://api.sb/shiftnote.app/events/event_1",
  "$type": "Event",
  "name": "Approval.expired",
  "at": "2026-10-22T12:00:00Z",
  "data": {
    "approval": {
      "$id": "https://api.sb/shiftnote.app/approvals/approval_1",
      "$type": "Approval",
      "status": "pending",
      "reason": "queued",
      "mode": "try",
      "by": "worker_1",
      "for": "party_1",
      "via": "API",
      "on": "Invoice.overdue",
      "thing": "invoice_1@v3",
      "input": {},
      "createdAt": "2026-10-22T12:00:00Z",
      "reserved": {
        "amount": 79,
        "currency": "USD"
      },
      "due": "2026-10-22T12:00:00Z",
      "priority": 1,
      "recurrence": "Weekly on Friday",
      "verdict": "allowed",
      "deadline": "2026-10-22T12:00:00Z"
    }
  },
  "seq": 1
}

Permissions

Which Access level may call each Verb. One-way Verbs wait their delay window.

Operationowneradminmemberviewer
Approval.createYesYesYesNo
Approval.getYesYesYesYes
Approval.listYesYesYesYes
Approval.updateYesYesYesNo
Approval.deleteYesYesYesNo
Approval.approveYesYesYesNo
Approval.refuseYesYesYesNo

Call it

Text
Approve the latest approval.

Example

The Sales Lead offers a 30% discount when its Limit is 20%. An Approval lands in the Founder's inbox, and the Quote goes out once approved.