Human approvals
Which calls need a person's approval, what Hub answers (428), where the person approves, and how the agent repeats the call.
In build (backend and dashboard)
Approvals are part of phase 1. The status codes, fields and header below are the agreed contract.
Some operations cannot be undone or reach people outside the company: issuing a document, creating, renewing, revoking or sending signing links, deleting drafts or address book entries, and starting or changing live tracking. For these, Hub does not take the agent's word. A person from the company approves the exact call in the dashboard. The operation catalog marks every such operation; changing a contractor's roles needs approval too.
The check runs on the server. It does not depend on the CLI, the MCP client or the SDK, and no flag, header or tool lets an agent approve its own call.
When approval applies
Approval is required when the operation needs it and the connection has "Require approval"
switched on. The company owner sets this per connection in the dashboard. Default: on for OAuth
connections (agents, connectors, the CLI after ecmrhub login), off for API keys.
The flow
| Step | Who | What happens |
|---|---|---|
| 1 | Agent | Calls the operation, for example issues a draft. |
| 2 | Hub | Records an approval request pinned to the exact operation and input, and answers 428 APPROVAL_REQUIRED with a link. |
| 3 | Agent | Shows the link to the user and waits. |
| 4 | Person | Opens the link in the dashboard, reviews the card and approves or rejects. |
| 5 | Agent | Repeats the same call with the approval id. Hub executes it and marks the approval as used. |
The 428 answer
{
"error": {
"status": 428,
"code": "APPROVAL_REQUIRED",
"message": "...",
"retryable": false,
"approval": {
"id": "6f1c2a9e-...",
"url": "https://ecmrhub-v2.lucaspiera.com/dashboard/approvals/6f1c2a9e-...",
"expiresAt": "2026-10-20T10:00:00.000Z",
"operation": "draft.publish",
"status": "pending"
}
}
}Sending the identical call again without the approval id returns the same pending approval; Hub does not create duplicates.
Repeating the call
Repeat the call with the same input and the approval id:
| Channel | How to pass the approval |
|---|---|
| HTTP | Header x-ecmr-approval: <id> |
| SDK | Write option approval: "<id>" |
| CLI | Run error.approval.retry (the same command plus --approval <id>); ecmrhub approvals wait <id> blocks until a decision |
| MCP | Argument approvalId on the same tool |
The approval is pinned to a SHA-256 digest of the operation name and its validated input (keys sorted). It is valid for 24 hours and can be used once.
| Situation | Answer | What to do |
|---|---|---|
| Still waiting for the person | 428 APPROVAL_REQUIRED | Wait; remind the user of the link |
| Approved, same input | The operation's normal result | Done; the approval is now used |
| Approved, input changed | 409 APPROVAL_MISMATCH | Repeat the original input, or start over without the id to request a new approval |
| Rejected | 403 APPROVAL_REJECTED | Stop and ask the user what to change |
| Older than 24 hours | 410 APPROVAL_EXPIRED | Call again without the id to request a new approval |
| Already used | 409 APPROVAL_CONSUMED | The call already ran; read the result (e.g. list documents) instead of repeating |
| Unknown id, or another company's | 404 APPROVAL_NOT_FOUND | Check the id you passed |
| 100 approvals pending for this connection | 429 APPROVAL_LIMIT | Ask the user to decide the pending ones first |
Checking status
ecmrhub approvals list --status pending --json # approvals this connection requested
ecmrhub approvals status <id> --json # one approval
ecmrhub approvals wait <id> --timeout 300 --json # exit 0 approved, 4 rejected, 1 expired or used, 5 still pendingOver HTTP: GET /functions/v1/approvals/<id> with the same token that made the request. A
connection or API key sees only the approvals it requested; a person sees the whole company. An
approval has status pending, approved, rejected, consumed or expired, the operation,
the input, who requested it and when it expires. Approving and rejecting is only possible for a
signed-in person in the dashboard; connections and API keys get 403.
What to tell the user
Keep it short and concrete, with the link:
I prepared consignment note CMR 2026/104 (Warsaw to Lyon, 33 pallets). Please approve issuing it:
https://ecmrhub-v2.lucaspiera.com/dashboard/approvals/6f1c2a9e-.... I will issue it as soon as you approve.
Do not change the data while waiting. If the user asks for a change, make it, then call again without the old approval id: the change needs a new approval.