---
title: "The Approval Engine"
category: "operations"
canonical: "https://www.ai-go.app/en-US/docs/guide/approval-system"
locale: "en-US"
last_updated: "2026-08-10"
source: "AI GO 開發者文件（https://www.ai-go.app）"
---

# The Approval Engine

Approvals in AI GO are not an "approvals module" — they are **an interceptor sitting on the data write path**. Which means: whether data arrives from a Custom App, the REST API, or the AI assistant, approvals apply. There is no route around them.

---

## How it works

In a typical system, creating a leave request or purchase order writes immediately. AI GO differs:

1. **Write interception** — once an administrator enables an approval workflow on a table, writes to it are intercepted
2. **Assignment** — the platform derives qualified approvers from the workflow definition, notifies them, and creates an approval request
3. **Approval callback** — when every stage passes, the platform executes the originally intercepted operation, including all the business engines it cascades into (invoicing, stock deduction, journal entries)

A rejection at any stage returns the record to draft for revision and resubmission.

### The three write types are intercepted differently

| Operation | Interception behavior |
|---|---|
| **Insert** | The record is held pending and takes effect on approval |
| **Update** | **Not executed.** The payload is held and applied automatically on approval |
| **Delete** | **Not executed.** Performed automatically on approval |

The distinction matters: updates and deletes are **not** written and then rolled back — they simply do not run. The caller receives a pending-approval exception carrying a request identifier.

**Callers should not retry** — retrying only creates duplicate requests. Catch the exception and tell the user the change was submitted for approval.

---

## Configuring a workflow

**Entry point**: Dashboard → System & Operations → Approval Workflow Engine. Requires `system.admin`.

### 1. Choose the target

Select the table the workflow applies to (sales orders, leave requests, vendor bills, and so on). **One table can have only one active workflow.**

### 2. Trigger conditions

Approvals can be required only under specific conditions — "purchase orders above 100,000 require manager sign-off", for instance. Anything below the threshold passes straight through, so you don't manufacture unnecessary approval load.

Conditions can also route to different flows based on record field values.

### 3. Define stages

Create ordered stages (department manager first, finance director second). Each stage can specify:

- Whether it is required
- **Approval type** — one approver suffices, or all listed approvers must sign (parallel approval)

### 4. Define qualified approvers

Each stage specifies approver eligibility, with three rule types:

| Rule | Notes |
|---|---|
| Specific user | A named account |
| Role | Everyone holding that role (all finance managers, say) |
| **Dynamic department manager** | Resolved from the requester's department manager, with a configurable number of levels upward |

Dynamic department manager is the most practical — org changes don't require revisiting workflow definitions.

---

## Integrating into a Custom App

The approval SDK lets you build approvals directly into an application so users never switch to the Dashboard.

### Server side

```python
def execute(ctx):
    # The triggering user's own queue
    pending = ctx.approval.list_pending()

    # A record's approval state, with per-stage detail
    status = ctx.approval.get_record_status('sale_orders', order_id)

    # Approve / reject as the triggering user
    ctx.approval.approve(line_id, comment='Verified')
    ctx.approval.reject(line_id, comment='Amount does not match the quote')

    # Cancel a request (requester only, while pending)
    ctx.approval.cancel(request_id, reason='Re-evaluating')
```

### Front end

`src/approval.ts` provides the matching front-end methods — enough to assemble an approval panel showing history, accepting a comment, and approving or rejecting in one click.

### Identity binding

The approval SDK is **bound to user identity**, which sets it apart from the other SDKs:

- `list_pending()` returns the **triggering user's own** queue, not the organization's
- `approve()` verifies the triggering user is a qualified approver for that stage and refuses otherwise
- Calls without a platform user identity (such as schedules) are rejected

You cannot use an application's privileges to approve on someone else's behalf.

---

## Integrating via API

External systems can integrate the full mechanism:

```
GET  /api/v1/approvals/record/{res_model}/{res_id}
POST /api/v1/approvals/{request_line_id}/approve
POST /api/v1/approvals/{request_line_id}/reject
POST /api/v1/approvals/{request_id}/retry
```

**Status query** returns each stage's state, approver name, comment, and timestamp, or empty if no approval was initiated.

**Approve** takes an approval line identifier and a comment. The system verifies eligibility; the final stage triggers the callback automatically.

**Reject** resets the record to draft for revision and resubmission.

**Retry callback** is for the case where approval fully passed but the callback failed for external reasons. An administrator can retry it manually. A failed callback does not roll the approval state back — it is an independent remediation.

---

## Common design patterns

### Tell users "submitted for approval", not "failed"

```python
def execute(ctx):
    try:
        ctx.db.update('sale_orders', order_id, {'state': 'sale'})
        return ctx.response.json({'status': 'updated'})
    except Exception as e:
        if 'approval' in str(e).lower():
            return ctx.response.json({
                'status': 'pending_approval',
                'message': 'Change submitted for approval; it will apply once approved'
            })
        raise
```

### Show approval state in lists

Batch-query with `get_record_status` and surface "stage 2 of 2 — finance director" directly in the list, so users don't have to open each record.

### A manager's task panel

Use `list_pending()` to build a to-do block on the application's home screen. This is the approval SDK's highest-value use — managers never log into a separate system to sign off.

---

## Boundaries and caveats

- **One workflow per table** — use conditional routing rather than multiple workflows when different paths are needed
- **Ordering relative to business engines** — callbacks run after approval, so invoicing, stock deduction, and journal entries happen at approval time, not at submission
- **Scheduled writes** — approval guards still apply; the approval UI shows the requester as "API"
- **Auditing** — every approval decision is fully recorded, including approver, timestamp, and comment