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

AttributeContents
ctx.paramsParameters passed by the caller
ctx.app_idThe running application's identifier
ctx.tenant_idThe owning organization's identifier
ctx.action_nameThe action currently executing
ctx.envRuntime environment (online / dev)
ctx.user_idThe triggering user's identifier
ctx.user_rolesThe triggering user's role names (read-only snapshot)
ctx.user_permissionsThe 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.

MethodPurpose
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.

MethodWhat 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

MethodPurpose
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.


MethodPurpose
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

MethodPurpose
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).
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

MethodPurpose
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

MethodPurpose
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

MethodPurpose
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

MethodPurpose
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

MethodPurpose
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.

RiskScopesApproval
Lowdb.read, data_table.read, messaging.read, approval.read, cryptoAdministrator approval
Highdb.write, data_table.write, erp.*, approval.decide, approval.cancel, knowledge.read, messaging.write, messaging.channel, http, mcp, secret.readAdministrator 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.