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:
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".
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:
- The third positional argument is
method(a string), not the body. Pass bodies as a keyword argument. calldoes not raise. It returns{status, headers, data}— always checkstatus. On failure,datacarrieserror_type,error, andfix(a single sentence you can relay to the user as a remedy).
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.
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:
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.