---
title: "What Your App Gets for Free: The SDK"
category: "app-development"
canonical: "https://www.ai-go.app/en-US/docs/guide/app-sdk"
locale: "en-US"
last_updated: "2026-08-10"
source: "AI GO 開發者文件（https://www.ai-go.app）"
---

# What Your App Gets for Free: The SDK

This is the most important chapter in this guide.

When you build on AI GO, a large class of work **does not need doing** — database connections, tenant isolation, permission checks, ERP business logic, approval workflows, vector search, a safe outbound channel for external APIs, secret storage, cryptography. The platform pre-builds all of it and hands it to you as an SDK.

The SDK has two sides: **the server-side `ctx` object** (used inside actions) and **six front-end TypeScript SDKs**.

---

## Server side: the `ctx` object

Every action is a function receiving `ctx`:

```python
def execute(ctx):
    ...
```

Everything the platform offers hangs off it.

### Runtime context attributes

| Attribute | Contents |
|---|---|
| `ctx.params` | Parameters passed by the caller |
| `ctx.app_id` | The running application's identifier |
| `ctx.tenant_id` | The owning organization's identifier |
| `ctx.action_name` | The action currently executing |
| `ctx.env` | Runtime environment (`online` / `dev`) |
| `ctx.user_id` | The triggering user's identifier |
| `ctx.user_roles` | The triggering user's role names (read-only snapshot) |
| `ctx.user_permissions` | The triggering user's permission tags (read-only snapshot) |

**Check authorization with `user_permissions`, not `user_roles`** — role names can be renamed; permission tags are stable identifiers.

---

## `ctx.db` — data access

Reaches both built-in ERP tables and custom tables.

| Method | Purpose |
|---|---|
| `query(table, filters, order_by, limit, offset)` | Query an ERP table |
| `insert(table, data)` | Insert into an ERP table |
| `update(table, row_id, data)` | Update an ERP row |
| `remove(table, row_id)` | Delete an ERP row |
| `list_tables()` | List the organization's custom tables |
| `query_table(name, filters, ...)` | Query a custom table |
| `insert_row(name, data)` | Insert a custom table row |
| `update_row(name, row_id, data)` | Update a custom table row |
| `delete_row(name, row_id)` | Delete a custom table row |

**Typical use**: anywhere you read or write business data. Queries support full filtering, sorting, and pagination — not just "fetch everything".

```python
orders = ctx.db.query('sale_orders',
    filters=[
        {'column': 'state', 'op': 'eq', 'value': 'sale'},
        {'column': 'amount_total', 'op': 'gte', 'value': 10000},
    ],
    order_by=[{'column': 'created_at', 'direction': 'desc'}],
    limit=50)
```

**Authorization**: reads need `db.read` / `data_table.read`; writes need `db.write` / `data_table.write`. ERP tables are additionally bound by reference declarations — undeclared tables and columns are unreachable.

---

## `ctx.erp` — trigger the business engines

The most underestimated module. It does not "write a row" — it **triggers the platform's complete pre-built business processes**.

| Method | What it triggers |
|---|---|
| `confirm_sale_order(id)` | Confirm a sales order (cascades into fulfillment and invoicing readiness) |
| `confirm_purchase_order(id)` | Confirm a purchase order |
| `create_invoice(order_id)` | Issue a customer invoice from a sales order. **`order_id` accepts a list** — several orders merge into one invoice |
| `create_bill(order_id)` | Create a vendor bill from a purchase order. Also supports list merging |
| `validate_picking(id)` | Validate a picking (cascades into stock moves and valuation) |
| `post_move(id)` | Post a journal entry |
| `confirm_payment(id)` | Confirm a payment (cascades into automatic reconciliation) |
| `reconcile_payment(id)` | Re-run reconciliation for an already-posted payment |
| `confirm_payroll_run(id)` | Confirm a payroll run (cascades into accrual entries) |
| `cancel_payroll_run(id)` | Cancel a payroll run (auto-generates reversing entries) |

**Typical use**: you build a "quick close" tool for sales. The user presses one button and order confirmation, invoicing, stock deduction, and journal entries all cascade from a single `ctx.erp` call — you never need to know how the accounts or tax rates are derived.

Merged invoicing is worth calling out: passing a list of order IDs consolidates all their lines into **one** invoice (same customer, same organization required). That solves the common "one invoice per month for account customers" requirement outright.

**Authorization**: each method maps to its own scope (for example `erp.sale_order.confirm`); all are high-risk.

---

## `ctx.approval` — approvals

| Method | Purpose |
|---|---|
| `list_pending()` | The **triggering user's own** approval queue |
| `get_record_status(res_model, res_id)` | Approval state and per-stage detail for a record |
| `approve(request_line_id, comment)` | Approve one stage as the triggering user |
| `reject(request_line_id, comment)` | Reject as the triggering user |
| `cancel(request_id, reason)` | Cancel a request (requester only, while pending) |

**Typical use**: a "my tasks" panel inside your department tool, so managers never have to open the Dashboard to sign off.

**Important semantics**: these methods are **bound to user identity**. `approve` verifies the triggering user is a qualified approver for that stage and refuses otherwise. Passing the final stage automatically executes the originally intercepted operation.

**Authorization**: `approval.read` (low risk) for reads, `approval.decide` (high risk) for decisions.

---

## `ctx.knowledge` — enterprise knowledge search

| Method | Purpose |
|---|---|
| `search(query, top_k, score_threshold)` | Vector search returning passages with relevance scores |
| `get_content(file_id)` | Full parsed text of one knowledge file |

**Typical use**: an internal Q&A assistant. Retrieve context from policies and product documents, then generate the answer with your own model key.

**Design note**: this is **retrieval only, no generation** — no language model is invoked, and raw passages come back. Generation stays in your own logic with your own key, so cost and model choice remain yours. Results automatically respect file-level access policies (Chapter 6).

**Authorization**: `knowledge.read`, high risk.

---

## `ctx.http` — call external APIs

| Method | Purpose |
|---|---|
| `call(service, path, method, body, headers, params)` | Call an API through an authorized external service |
| `fetch(url, ...)` | Fetch an arbitrary public URL (security limits still apply) |

**Typical use**: logistics tracking, SMS gateways, e-invoicing providers, your own internal systems.

**Two easy mistakes**:

1. **The third positional argument is `method` (a string), not the body.** Pass bodies as a keyword argument.
2. **`call` does not raise.** It returns `{status, headers, data}` — always check `status`. On failure, `data` carries `error_type`, `error`, and `fix` (a single sentence you can relay to the user as a remedy).

```python
resp = ctx.http.call('logistics', '/track', method='POST',
                     body={'no': ctx.params['tracking_no']})
if resp['status'] != 200:
    return ctx.response.json({'error': resp['data'].get('fix')})
```

**Authorization**: `http`, high risk, and the target domain must be allowlisted.

---

## `ctx.messaging` — communication center

| Method | Purpose |
|---|---|
| `list_channels()` / `add_channel()` / `update_channel()` / `remove_channel()` | Manage channels |
| `inject_inbound()` | Inject an inbound message |
| `save_ai_outbound()` | Store an AI-generated reply |
| `get_ai_config()` | Read reply configuration |

**Typical use**: a support console inside your application — pull messages from external channels, bind them to CRM customers, and let staff reply from one screen.

**Authorization**: `messaging.read` (low risk) for reads, `messaging.write` and `messaging.channel` (high risk) for writes and channel management.

---

## `ctx.secrets` — secret storage

| Method | Purpose |
|---|---|
| `get(key_name)` | Retrieve a secret value |
| `list_keys()` | List available secret names |

**Typical use**: your application calls a third-party service or your own model API. Keys live in App Secrets — **never in source code, never sent to the browser**.

**Authorization**: `secret.read`, high risk.

---

## `ctx.crypto` — cryptography

| Method | Purpose |
|---|---|
| `hash(algorithm, data)` | Hashing (sha256, etc.) |
| `hmac_sign(key_name, data)` | HMAC signature (by key **name**) |
| `base64_encode(data)` / `base64_decode(data)` | Base64 |
| `aes_encrypt(key_name, plaintext)` | AES-256 encryption |
| `aes_decrypt(key_name, ciphertext, iv)` | AES-256 decryption |

**Typical use**: verifying webhook signatures, signing outbound API requests, encrypting sensitive fields at rest.

Note that `hmac_sign` and the AES methods take a **key name**, not a key value — the actual secret never passes through your code.

**Authorization**: `crypto`, low risk.

---

## `ctx.mcp` — external tool protocol

| Method | Purpose |
|---|---|
| `execute(task, wait=True)` | Run a task synchronously |
| `trigger(task)` | Fire asynchronously, returns a task id |
| `get_status(task_id)` | Poll an async task |

**Authorization**: `mcp`, high risk.

---

## `ctx.response` and `ctx.csv` — output

| Method | Purpose |
|---|---|
| `ctx.response.json(data)` | Return JSON |
| `ctx.response.file(content, filename, mime)` | Return a file download |
| `ctx.csv.export(rows, columns, filename)` | Export CSV in one line |

These run locally in the runtime and need no scope.

---

## Front-end SDKs

Six platform-provided TypeScript SDKs live under `src/`, ready to use.

### `src/db.ts` — ERP tables

Query and write referenced ERP tables directly from the front end, mirroring `ctx.db`. Suited to list and detail views.

### `src/api.ts` — custom tables

Access the organization's custom tables, with the same filtering, sorting, and pagination.

### `src/action.ts` — invoke server-side actions

The official channel for calling actions. Anything needing secrets, elevated privilege, or hidden logic belongs in an action invoked from here.

```ts
import { callAction } from './action';

const result = await callAction('confirm_orders', { ids: selectedIds });
```

### `src/approval.ts` — approvals (internal only)

Fetch pending items, query status, approve and reject. Enough to assemble a complete approval panel.

### `src/user.ts` — user context (internal only)

Read the current user's roles and permission tags to drive the interface:

```ts
import { getUser } from './user';

const user = await getUser();
if (user.permissions.includes('accounting.write')) {
  // show the posting button
}
```

**Note**: front-end permission checks are **UX polish, not a security boundary**. Real authorization runs server-side — a hidden button is still callable via the API and will still be rejected.

### `src/auth.ts` — sign-up and sign-in (external only)

Registration, login, logout, and password change flows for external app users.

---

## Authorization at a glance

Every SDK method maps to a **scope** (a capability group). An application declares which scopes it needs; an administrator approves them.

| Risk | Scopes | Approval |
|---|---|---|
| **Low** | `db.read`, `data_table.read`, `messaging.read`, `approval.read`, `crypto` | Administrator approval |
| **High** | `db.write`, `data_table.write`, `erp.*`, `approval.decide`, `approval.cancel`, `knowledge.read`, `messaging.write`, `messaging.channel`, `http`, `mcp`, `secret.read` | Administrator approval **plus the owner re-entering their password** |

Scope is the switch for a **capability group**; finer boundaries come from other mechanisms — ERP tables from reference declarations, external services from the domain allowlist, secrets from the available key list. The two layers stack.

Every SDK call is audited. See Chapter 10.