# MainSDK interfaces

Generated from docs/catalog/interfaces.yaml by tools/build_skill.py. Do not edit by hand.

Use these call shapes exactly. A call or option not listed here or in the linked docs page does not exist yet.
To save tokens, load only the category you need from `references/categories/` (index in SKILL.md).

## Identity and access

### `auth`: Sign-in and sessions

Sign people in with GitHub, passkeys, company SSO or WhatsApp, and keep their sessions safe.

- Status: Building for 0.3 · ships stable
- Drivers: default `github`; also `clerk`, `auth0`, `better-auth`, `workos`, `supabase`, `local`
- Calls: `auth.session(req)`, `auth.require(req, { role, aal, fresh })`, `auth.passkeys.register()`, `auth.passkeys.signIn()`, `auth.whatsapp.send()`, `auth.sso.enable()`, `auth.scim.enable()`, `auth.challenge.start({ method })`, `auth.challenge.verify({ message, signature })`, `auth.device.start({ scope })`, `auth.devices(user)`, `auth.devices.revoke(user, deviceId)`, `auth.orgs.create({ name, slug?, region })`, `auth.orgs.list()`, `auth.orgs.switch(req, tenantId)`, `auth.members.list({ cursor?, limit? })`, `auth.members.setRoles(userId, roles)`, `auth.members.remove(userId)`, `auth.teams.create(name)`, `auth.teams.list()`, `auth.teams.add(teamId, userId)`, `auth.teams.remove(teamId, userId)`, `auth.invites.create({ email, roles, teams?, expiresIn? })`, `auth.invites.accept(req, token)`, `auth.invites.revoke(inviteId)`, `auth.domains.add(domain)`, `auth.domains.verify(domain)`, `auth.domains.autoJoin(domain, { role } | false)`, `auth.sso.require(tenantId, { except? })`, `new Auth(opts)`, `new StepUpRequired(o)`, `assessRisk(input, cfg)`, `createAuth(opts)`, `isPhishingResistantMethod(method)`, `maskPhone(e164)`, `normalizePhone(input)`, `auth.handle(req, fn)`, `auth.passwords.register(req, { email, password, name? })`, `auth.passwords.signIn(req, { email, password })`, `auth.link.issue({ email } | { phone }, { bind?, req? })`, `auth.link.verify(req, token)`, `auth.oauth.start(provider, { redirectUri, returnTo? })`, `auth.oauth.callback(req)`, `auth.stepUp.start({ pending, challenge, method })`, `auth.stepUp.complete(req, { pending, challenge, method, ... })`, `auth.recover(user, { via, req? })`, `auth.recover.confirm(challengeId, code)`, `auth.recover.cancel(session)`, `auth.recover.complete(req, recovery)`, `auth.recover.withCode(req, user, code)`, `auth.recover.withPasskey(req, user, assertion)`, `auth.recover.generateCodes(session)`, `auth.recover.codesLeft(user)`, `auth.sso.get()`, `auth.sso.signIn({ email } | { org }, { redirectUri, returnTo? })`, `auth.sso.callback(req)`, `auth.sso.saml.metadata(org, { acsUrl })`, `auth.sso.saml.acs(req)`, `auth.risk(req, { userId })`, `auth.signOut(req)`, `auth.signOutEverywhere(req)`, `auth.onRisk(fn)`, `auth.raw()`
- Docs: https://mainsdk.dev/docs/interfaces/auth (Markdown: https://mainsdk.dev/docs/interfaces/auth.md)
- Check codes: MAIN-AUTH-0053, MAIN-AUTH-0055

### `connections`: Connections to other services

Let a person connect Gmail, GitHub or Slack once, so an app agent can act for them within the scopes they agreed to.

- Status: Building for 0.3 · ships experimental
- Drivers: no default; also `google`, `github`, `slack`, `microsoft`, `notion`, `atlassian`, `airtable`, `partner`, `local`
- Calls: `connections.connect(req, provider, opts)`, `connections.callback(req)`, `connections.fetch(provider, url, init)`, `connections.list(opts?)`, `connections.revoke(id, opts?)`, `connections.describe(provider, scopes?)`
- Docs: https://mainsdk.dev/docs/interfaces/connections (Markdown: https://mainsdk.dev/docs/interfaces/connections.md)
- Check codes: MAIN-CONN-0020, MAIN-CONN-0021

### `verify`: Phone, email and identity checks

Confirm a phone number, an email address or a person's identity at the level a market requires, with no answer that tells who has an account.

- Status: Building for 0.7 · ships beta
- Drivers: default `twilio-verify`; also `bird`, `local`, `digilocker`, `onfido`, `persona`
- Calls: `verify.phone({ number, via?, for?, locale?, req? })`, `verify.phone.check({ id, code })`, `verify.email({ address, for?, req? })`, `verify.email.confirm(token)`, `verify.identity({ level, market, claims, need?, purpose, user, consent, redirect? })`, `verify.identity.complete({ id, code? })`, `createVerify(options)`, `parseVerify(config)`, `verifyHosts(config)`, `normalizeNumber(input)`, `normalizeAddress(input)`, `maskNumber(e164)`, `reaches(driver, level)`, `twilioVerify(options)`, `bird(options)`, `localPhone()`, `phoneFromConfig(id, { settings, secret, fetch })`, `failure(provider, { retryable, cause })`, `onfido(options)`, `persona(options)`, `digilockerIdentity(wallet)`, `localIdentity(fixture?, options?)`, `deriveClaims(attrs, { asked, nowMs })`, `createIdentity(options)`
- Docs: https://mainsdk.dev/docs/interfaces/verify (Markdown: https://mainsdk.dev/docs/interfaces/verify.md)
- Check codes: MAIN-VERIFY-0004, MAIN-VERIFY-0006

### `tenant`: Tenants

Give every request a tenant and keep each customer's records, files, keys and logs apart, in the region they chose.

- Status: Building for 0.3 · ships stable
- Drivers: default `builtin`
- Calls: `tenant.current()`, `tenant.maybe()`, `tenant.run(id, fn)`, `tenant.system(reason, fn)`, `tenant.anonymous(fn)`, `tenant.create({ name, kind?, region, slug?, plan? })`, `tenant.suspend(id, { reason })`, `tenant.resume(id)`, `tenant.export(id)`, `tenant.erase(id, { reason })`, `tenant.cancelErase(id)`, `new MemoryDirectory()`, `globMatch(globs, path)`, `overrideLoosens(direction, base, { value })`, `regionOfMarket(market)`, `uuidv7(now)`, `tenant.active()`, `tenant.actor()`, `tenant.as(who, fn)`, `tenant.audit(action, id, attributes?)`, `tenant.base`, `tenant.carry()`, `tenant.configValue(key)`, `tenant.forgetPerson(user)`, `tenant.handle(req, own, fn)`, `tenant.hold(id)`, `tenant.isGlobal(model)`, `tenant.isolationTarget()`, `tenant.loadOverrides(raw, base, direction)`, `tenant.personHash(user)`, `tenant.personalFor(person)`, `tenant.prefix(key)`, `tenant.principal()`, `tenant.register(driver)`, `tenant.release(id)`, `tenant.resolve(req, own?)`, `tenant.roles()`, `tenant.runJob(id, fn)`, `tenant.scope(call)`, `tenant.setPlan(id, plan)`, `tenant.store(stores)`, `tenant.subject()`, `tenant.sweep()`, `tenant.tag()`, `tenant.tenantOf(id)`, `tenant.verify(key, data, signature)`
- Docs: https://mainsdk.dev/docs/interfaces/tenant (Markdown: https://mainsdk.dev/docs/interfaces/tenant.md)
- Check codes: MAIN-TENANT-0010, MAIN-TENANT-0011, MAIN-TENANT-0012, MAIN-TENANT-0013

### `apikeys`: API keys for your customers

Issue each customer API keys with scopes, rotation without downtime, instant revoke and a rate limit, and verify them on every call.

- Status: Building for 0.3 · ships beta
- Drivers: default `db`; also `local`
- Calls: `apikeys.create({ tenant?, name, scopes, expiresAt?, rate? })`, `apikeys.verify(req)`, `apikeys.list({ limit?, cursor? })`, `apikeys.rotate(keyId, { overlap?, expiresAt? })`, `apikeys.revoke(keyId, { reason })`, `API_KEY_DDL(const)`, `new ApiKeys(o)`, `dbApiKeyStore(o)`, `isValidKey(text, prefix)`, `keyRedactor(prefix)`, `localApiKeyStore()`, `parseApiKeys(raw, project?)`
- Docs: https://mainsdk.dev/docs/interfaces/apikeys (Markdown: https://mainsdk.dev/docs/interfaces/apikeys.md)
- Check codes: MAIN-APIKEY-0010, MAIN-APIKEY-0011

### `access`: Permissions

Decide who can do what to which record, with roles, attributes or relations on any engine.

- Status: Building for 0.3 · ships stable
- Drivers: default `cedar`; also `opa`, `openfga`, `spicedb`, `cerbos`, `permit`, `oso`, `avp`
- Calls: `access.check({ subject, action, resource, context })`, `access.decide({ subject, action, resource, context })`, `access.require(subject, action, resource)`, `access.explain(decisionId)`, `access.can()`, `access.role().can()`, `access.list()`, `access.relate(team, "editor", folder)`, `access.rule(name, fn)`, `access.who(action, resource)`, `access.attribute(name, { subject, asset })`, `access.attributes.register(name, source)`, `access.checkWith(request, needs)`, `access.setGroups(fn)`
- Docs: https://mainsdk.dev/docs/interfaces/access (Markdown: https://mainsdk.dev/docs/interfaces/access.md)
- Check codes: MAIN-ACCESS-0004, MAIN-ACCESS-0011

### `policy`: Policy decisions

Decide every sensitive call in process from policies kept in git, and explain each decision.

- Status: Building for 0.2 · ships stable
- Drivers: default `builtin`; also `opa`
- Calls: `policy.decide()`, `policy.explain()`, `policy.test()`, `policy.rollout()`, `policy.decideRemote(input)`
- Docs: https://mainsdk.dev/docs/interfaces/policy (Markdown: https://mainsdk.dev/docs/interfaces/policy.md)
- Check codes: MAIN-POLICY-0004

### `context`: Context and shared context

One request context every interface reads, and consent-gated, first-party shared context (settings and progress) between peer apps of one owner on one machine.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `context.current()`, `context.run(patch, fn)`, `context.capture()`, `context.restore(snap, fn)`, `context.shared.put({ purpose, attributes?, events? })`, `context.shared.get(purpose)`, `context.shared.list({ purpose?, cursor?, limit? })`, `context.shared.erase({ user?, purpose? })`, `context.identity.reset()`, `parseContext(raw, opts?)`, `contextJsonSchema(extras?)`, `deriveContextId(groupKey, { issuer, tenant, subject, epoch })`
- Docs: https://mainsdk.dev/docs/interfaces/context (Markdown: https://mainsdk.dev/docs/interfaces/context.md)
- Check codes: MAIN-CONTEXT-0001, MAIN-CONTEXT-0006

## Data

### `db`: Data and records

Read and write records that are private to their owner, stored in the owner's region, with labelled fields.

- Status: Building for 0.3 · ships stable
- Drivers: default `postgres`; also `neon`, `supabase`, `sqlite`
- Calls: `db.<model>.for(user).list()`, `db.<model>.for(user).get()`, `db.<model>.for(user).create()`, `db.<model>.for(user).update()`, `db.<model>.for(user).delete()`, `db.<model>.for(user).restore()`, `db.<model>.for(user).trashed().list()`, `db.<model>.public()`, `db.transaction()`, `model(schema, options)`, `etag(version)`, `ifMatch(request)`, `preconditionFailed(error)`, `new DbCollection(sql, tenancy, name, fields)`, `new FileCollection(name, file)`, `new LocalCollection(name)`, `openCollection(backend, name, fields)`, `new RuntimeDb(opts)`, `new Outbox(opts)`, `new VersionConflict(currentVersion, fromIfMatch?)`, `spanMs(v)`, `models(defs)`, `fingerprint(sql)`, `queryScope(kind, fn, opts?)`, `trackQueries(fn)`, `parseDbSettings(cfg, opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/db (Markdown: https://mainsdk.dev/docs/interfaces/db.md)
- Check codes: MAIN-DATA-0013, MAIN-DATA-0020, MAIN-DATA-0021, MAIN-DATA-0030, MAIN-DATA-0040, MAIN-DATA-0041, MAIN-DATA-0042, MAIN-DATA-0043, MAIN-DATA-0044, MAIN-DATA-0045, MAIN-DATA-0051, MAIN-DATA-0055, MAIN-DATA-0056, MAIN-DATA-0061, MAIN-DATA-0062, MAIN-DATA-0065, MAIN-DATA-0070, MAIN-DATA-0071, MAIN-DATA-0180, MAIN-DATA-0181, MAIN-DATA-0182

### `calendar`: Calendars

Store events with recurrence and time zones, import and export iCalendar, and show free/busy without showing details.

- Status: Building for 1.2 · ships experimental
- Drivers: default `db`; also `local`, `caldav`
- Calls: `calendar.createCalendar({ id?, name, owner? })`, `calendar.calendars()`, `calendar.create(cal, event)`, `calendar.get(id)`, `calendar.update(id, patch, { scope, occurrence?, version })`, `calendar.delete(id, { scope, occurrence? })`, `calendar.list({ calendars, from, to, cursor?, limit? })`, `calendar.freeBusy({ calendars?, members?, from, to })`, `calendar.import(cal, source)`, `calendar.export(cal, { from?, to? })`, `calendar.subscribe(cal)`
- Docs: https://mainsdk.dev/docs/interfaces/calendar (Markdown: https://mainsdk.dev/docs/interfaces/calendar.md)
- Check codes: MAIN-CAL-0001, MAIN-CAL-0002

### `imports`: Import files and pull data

Read CSV, XLSX, JSON and NDJSON files, map their columns with a confirmed mapping, and pull from outside sources on a schedule.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`; also `google-sheets`, `airtable`, `rest`
- Calls: `imports.preview(file, opts)`, `imports.file(file, opts)`, `imports.get(runId)`, `imports.list(opts?)`, `imports.cancel(runId, opts)`, `imports.source(name, opts)`, `imports.enable(name, opts?)`, `imports.disable(name)`, `imports.pull(name, opts?)`, `imports.conflicts(name, opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/imports (Markdown: https://mainsdk.dev/docs/interfaces/imports.md)
- Check codes: MAIN-IMPORT-0020

### `kv`: Key-value store and cache

Keep small values and counters under a key with an optional expiry, scoped to the tenant.

- Status: Building for 0.3 · ships beta
- Drivers: default `local`; also `redis`, `upstash`, `vercel-runtime-cache`, `cloudflare-kv`
- Calls: `kv.get(key)`, `kv.set(key, value, { ttl })`, `kv.delete(key)`, `kv.incr(key, { by, ttl })`, `new RespClient(opts)`, `new RespError(message)`, `new RespSubscriber(opts, backoff)`, `redisKv(o)`, `localKv(o?)`
- Docs: https://mainsdk.dev/docs/interfaces/kv (Markdown: https://mainsdk.dev/docs/interfaces/kv.md)

### `cache`: Cache

Memoise any call with keys that always carry the tenant and region, and keep personal responses out of shared caches.

- Status: Building for 0.4 · ships beta
- Drivers: default `kv`; also `local`
- Calls: `cache.wrap(key, fn, { ttl, swr?, tags?, scope? })`, `cache.get(key, { scope? })`, `cache.set(key, value, { ttl, tags?, scope? })`, `cache.delete(key | { tag }, { scope? })`, `new Cache(opts)`, `parseCache(raw, hasKv?)`, `cacheIssues(raw)`, `serveRead(action, req, opts)`, `etagMatches(header, etag)`, `weakEtag(output)`
- Docs: https://mainsdk.dev/docs/interfaces/cache (Markdown: https://mainsdk.dev/docs/interfaces/cache.md)
- Check codes: MAIN-CACHE-0011, MAIN-CACHE-0012

### `search`: Search

Index records and search them with facets, highlighting and typo tolerance, filtered by tenant and access inside the query.

- Status: Building for 0.4 · ships beta
- Drivers: default `db`; also `typesense`, `meilisearch`, `local`, algolia (planned), pgvector (planned)
- Calls: `search.index()`, `search.query({ for: user })`, `search.remove(index, ids)`, `search.status(index)`, `search.reindex(index, { region?, tenant? })`
- Docs: https://mainsdk.dev/docs/interfaces/search (Markdown: https://mainsdk.dev/docs/interfaces/search.md)
- Check codes: MAIN-SEARCH-0010, MAIN-SEARCH-0011, MAIN-SEARCH-0012, MAIN-SEARCH-0013, MAIN-SEARCH-0014

### `feeds`: Feeds and timelines

Follows, blocks and mutes, home timelines that fan out on write and on read, and trending per region, with a feed that has an end.

- Status: Building for 0.4 · ships experimental
- Drivers: default `db`; also `local`
- Calls: `feeds.follow(follower, target)`, `feeds.unfollow(follower, target)`, `feeds.followers(user)`, `feeds.following(user)`, `feeds.counts(user)`, `feeds.requests(user)`, `feeds.accept(user, from)`, `feeds.decline(user, from)`, `feeds.block(user, target)`, `feeds.unblock(user, target)`, `feeds.mute(user, target, { until? })`, `feeds.unmute(user, target)`, `feeds.relation(a, b)`, `feeds.publish(author, { id, visibility })`, `feeds.edit(author, id)`, `feeds.remove(by, id)`, `feeds.timeline(user, { order?, cursor?, continue? })`, `feeds.profile(viewer, author)`, `feeds.ranker(fn)`, `feeds.count(key, { by })`, `feeds.trending({ window?, k? })`
- Docs: https://mainsdk.dev/docs/interfaces/feeds (Markdown: https://mainsdk.dev/docs/interfaces/feeds.md)
- Check codes: MAIN-FEED-0010, MAIN-FEED-0011, MAIN-FEED-0012, MAIN-FEED-0013

### `sync`: Offline-first replication

Keep a local copy of a person's rows on their device, work offline, and merge changes back to the server without losing edits.

- Status: Building for 1.1 · ships experimental
- Drivers: default `local`; also `electric`, `powersync`
- Calls: `sync.open({ tables, for, transport, storage?, now?, ids? })`, `replica.status()`, `replica.flush({ signal })`, `replica.conflicts()`, `replica.close()`, `table.insert(values, { id? })`, `table.update(id, patch)`, `table.delete(id)`, `table.get(id)`, `table.list()`, `server.push(who, request)`, `server.pull(who, request)`, `server.erase(subject, { tenant })`, `server.transport(who)`, `server.handler(who)`, `httpTransport(url, { fetch })`, `memoryStorage()`
- Docs: https://mainsdk.dev/docs/interfaces/sync (Markdown: https://mainsdk.dev/docs/interfaces/sync.md)
- Check codes: MAIN-SYNC-0007

## AI and agents

### `ai`: AI calls

Call any model through one interface, with keys out of your code, limits per user and every call audited.

- Status: Building for 0.3 · ships beta
- Drivers: default `local`; also `anthropic`, `openai`, `gateway`, `bedrock`, `ollama`, `llama-cpp`, `openai-compatible`
- Calls: `ai.generate({ model, prompt, feature, user })`, `ai.embed({ model, input, feature })`, `ai.tool(action)`, `ai.stream({ model, prompt | messages, feature })`, `ai.extract({ input, schema, model, feature })`, `ai.classify({ input, labels, model, feature })`, `ai.summarize({ input, length, model, feature })`, `ai.disclose(generation)`, `ai.extractor(schema, { model, feature })`, `ai.image.generate({ model, prompt, feature })`, `ai.image.edit({ model, image, prompt, feature })`, `ai.image.inpaint({ model, image, mask, prompt, feature })`, `ai.image.variations({ model, image, count, feature })`, `ai.credentials.verify(file, { user })`, `ai.run({ model, feature, agent, goal, tools, maxSteps, budget })`, `ai.run.stream(opts)`, `ai.run.resume(runId)`, `ai.run.get(runId)`, `ai.batch.submit({ model, feature, items })`, `ai.batch.get(id)`, `ai.batch.cancel(id, { reason })`, `ai.video.generate({ model, prompt, feature, seconds })`, `ai.video.get(id)`, `ai.video.cancel(id, { reason })`, `ai.voice.session({ model, voice, instructions, feature, agent })`, `ai.voice.calls.handle(req, session)`, `ai.voice.calls.dial({ to, from, session })`, `ai.voice.calls.upgrade({ req, socket, head })`, `ai.voice.rooms.join({ room, session })`, `ai.voice.rooms.token({ room, user })`, `ai.speech({ model, voice, text | input, format, feature })`, `ai.transcribe({ model, audio, feature })`, `ai.transcribe.stream({ model, audio, format, sampleRate, feature })`, `ai.voices.list()`, `ai.voices.clone({ name, samples, statement, feature })`, `ai.voices.delete(id, { reason })`, `ai.voices.share(id, share)`, `createAi(opts)`, `new ProviderError(code, message, opts?)`, `fakeImageDriver(opts?)`, `fileStore(dir)`, `gatewayImageDriver(opts)`, `inlineWorkflows(timers?)`, `localAiDriver(opts?)`, `localImageDriver(opts?)`, `localSigner(opts)`, `looksLikeInjection(text)`, `meetsData(declared, required)`, `memoryStore()`, `openaiImageDriver(opts)`, `parseSse(body)`, `signAsset(asset, mime, { req, signer })`, `verifyAsset(asset, mime, opts?)`, `ai.html(text, opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/ai (Markdown: https://mainsdk.dev/docs/interfaces/ai.md)
- Check codes: MAIN-AI-0001, MAIN-AI-0044, MAIN-AI-0049, MAIN-AI-0068, MAIN-AI-0070, MAIN-AI-0079, MAIN-VOICE-0010, MAIN-VOICE-0011, MAIN-EVAL-0002, MAIN-EVAL-0003, MAIN-EVAL-0004

### `action`: Actions and agent tools

Write an action once and get an API endpoint, a server action, a CLI command and an MCP tool with the same checks.

- Status: Preview for 0.3 · ships beta
- Drivers: no default; also `mcp`, `http`, `server-action`, `cli`
- Calls: `action({ name, intent, input, risk, run })`, `mcpServer({ actions })`, `mcpClient({ servers })`, `new Idempotency(opts)`, `idempotencyKey(action, input)`, `idempotency(object: once, key, current)`, `signTools(actions, key)`, `new TokenExchange({ key, scopes })`, `toolHash(tool)`, `canonicalJson(value)`, `inputHash(value)`, `createMcpServer({ actions })`, `createMcpClient({ servers })`
- Docs: https://mainsdk.dev/docs/interfaces/action (Markdown: https://mainsdk.dev/docs/interfaces/action.md)

### `agents`: App agents

Give each agent your app runs an identity in the audit log, a budget, a person's approval for risky steps and a kill switch.

- Status: Building for 0.3 · ships experimental
- Drivers: default `builtin`
- Calls: `agents.reserve(call)`, `agents.settle(hold, actual)`, `agents.release(hold)`, `agents.record(call)`, `agents.check(call)`, `agents.ask(req)`, `agents.remaining(name)`, `agents.counters()`, `agents.attach(budget)`, `agents.admit(call)`, `agents.charge(call)`, `agents.run(call, fn)`, `agents.approve(approvalId, by)`, `agents.isApproved(approvalId)`, `agents.approvals.list()`, `agents.get(name)`, `agents.reload(config)`, `agents.close(approvalId, why)`, `agents.decline(approvalId, by)`, `agents.isDeclined(approvalId)`, `agents.isPending(approvalId)`, `agents.onApprove(fn)`, `agents.remainingSpend(name)`, `agents.request(req)`, `agents.pendingApprovals()`, `agents.onBudgetLow(fn)`
- Docs: https://mainsdk.dev/docs/interfaces/agents (Markdown: https://mainsdk.dev/docs/interfaces/agents.md)

### `memory`: Agent memory

Let app agents remember the people they serve, inside the tenant and the app, with untrusted recall, versions and forgetting you can prove.

- Status: Building for 0.4 · ships experimental
- Drivers: default `db`; also `local`, `mem0`, `zep`, `letta`, `pinecone`, `mcp`
- Calls: `memory.remember(input, { about, kind, ttl, label, source, key, session })`, `memory.recall(query, { about, kinds, k, asOf, halfLife, hops, session })`, `memory.context({ about, query, budget })`, `memory.revise(id, next, { reason })`, `memory.forget(target, { reason, dryRun })`, `memory.pin(id)`, `memory.unpin(id)`, `memory.history(id)`, `memory.learn(messages, { about, kinds, maxCost })`, `memory.relate({ from, predicate, to })`, `memory.consolidate({ about, reflect })`, `memory.export({ about, kinds })`, `memory.import(lines, { about, session })`, `memory.mcp({ compat })`, `memory.anthropicTool()`
- Docs: https://mainsdk.dev/docs/interfaces/memory (Markdown: https://mainsdk.dev/docs/interfaces/memory.md)
- Check codes: MAIN-MEM-0020, MAIN-MEM-0021, MAIN-MEM-0022, MAIN-MEM-0023, MAIN-MEM-0024, MAIN-MEM-0025

### `advisor`: Playbook advisor

Optionally let a model read signals and norms and propose one of the app's declared playbooks with a reason a person approves; off by default.

- Status: Building for 0.6 · ships experimental
- Drivers: default `builtin`
- Calls: `advisor.propose({ signals? })`, `advisor.get(id)`, `advisor.list({ cursor?, limit? })`
- Docs: https://mainsdk.dev/docs/interfaces/advisor (Markdown: https://mainsdk.dev/docs/interfaces/advisor.md)
- Check codes: MAIN-ADVISOR-0001, MAIN-OPS-0001

### `compute`: Remote GPU and long jobs

Run GPU and long jobs on your own Modal, Replicate or RunPod account, or a local program, as workflows with progress, cancel, cost limits and results in files.

- Status: Building for 0.6 · ships experimental
- Drivers: default `local`; also `modal`, `replicate`, `runpod`
- Calls: `compute.run(target, input, { files?, maxCost?, timeout?, agent?, idempotencyKey? })`, `compute.get(jobId)`, `compute.list({ target?, state?, cursor?, limit? })`, `compute.cancel(jobId, { reason })`, `compute.step(name, target, input, opts?)`, `compute.callback(call, request)`
- Docs: https://mainsdk.dev/docs/interfaces/compute (Markdown: https://mainsdk.dev/docs/interfaces/compute.md)
- Check codes: MAIN-COMPUTE-0001, MAIN-COMPUTE-0008, MAIN-COMPUTE-0013

### `ops`: Ops server

Let a coding agent ask a running app how it is, what it has seen and what a playbook would do, through nine MCP tools on a user-only socket, with every change waiting for a person.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `new OpsServer(opts)`, `parseOps(section, env)`, `opsIssues(section, ctx)`, `OPS_TOOL_NAMES(const: the nine tool names)`
- Docs: https://mainsdk.dev/docs/interfaces/ops (Markdown: https://mainsdk.dev/docs/interfaces/ops.md)
- Check codes: MAIN-OPS-0001

## Messaging and notifications

### `notify`: Notifications

Reach a person on push, WhatsApp, SMS or email with one call that respects their preferences and quiet hours.

- Status: Building for 0.3 · ships stable
- Drivers: no default; also `whatsapp-cloud`, `bird`, `msg91`, `twilio`, `local`, `fcm`, `apns`, `expo`, resend (planned), ses (planned), onesignal (planned), `slack`, `pagerduty`, `opsgenie`, `webhook`
- Calls: `notify(to, { template, data, category, channels?, urgent?, idempotencyKey? })`, `notify.preferences(user)`, `notify.preferences(user).set(patch)`, `notify.contact.get(user)`, `notify.contact.update(user, patch)`, `notify.consent.create(user, channel, opts?)`, `notify.consent.get(user, channel)`, `notify.consent.revoke(user, channel)`, `notify.release(id)`, `notify.broadcast({ id, template, data, category, to })`, `notify.rollout.get(id)`, `notify.rollout.resume(id, opts?)`, `notify.rollout.advance(id)`, `notify.raw()`, `notify.info()`, `notify.push.register()`, `createNotify(deps)`, `parseNotify(raw)`, `twilioSms(o)`, `msg91Sms(o)`, `msg91WhatsApp(o)`, `birdWhatsApp(o)`, `whatsappCloudChannel(o)`, `localChannel(id, o?)`
- Docs: https://mainsdk.dev/docs/interfaces/notify (Markdown: https://mainsdk.dev/docs/interfaces/notify.md)
- Check codes: MAIN-NOTIFY-0001, MAIN-NOTIFY-0006, MAIN-CHECK-0026

### `engagement`: Engagement journeys

Bring people back with reminder ladders that obey a weekly limit, quiet hours and a stop after ignored messages, with strict rules for children and no dark patterns.

- Status: Building for 0.4 · ships experimental
- Drivers: default `builtin`
- Calls: `journey(name, { version?, start, category }, fn)`, `ladder(user, rungs, { sendAt? })`, `engagement.send(user, { template, data?, journey?, rung? })`, `engagement.sendTime(user)`, `engagement.status(user)`, `engagement.results(flag, range)`, `engagement.copy(template, { locale, variant? })`, `engagement.guardian(child)`, `engagement.optOut(user, { channel, category?, via? })`, `engagement.stopByHandle(handle, { channel, category?, via? })`, `engagement.keyword(user, text, { channel? })`, `engagement.preferences(user).set({ maxPerWeek? })`, `engagement.apply(pushed)`, `engagement.register(journey)`, `engagement.start(journey, user)`, `engagement.attach()`, `engagement.erase(userId)`, `createEngagement(deps)`, `parseEngagement(raw)`
- Docs: https://mainsdk.dev/docs/interfaces/engagement (Markdown: https://mainsdk.dev/docs/interfaces/engagement.md)
- Check codes: MAIN-ENGAGE-0010, MAIN-ENGAGE-0011, MAIN-ENGAGE-0012, MAIN-ENGAGE-0013, MAIN-ENGAGE-0014

### `email`: Email

Send transactional and marketing email, with one-click unsubscribe and no tracking pixels by default.

- Status: Building for 0.3 · ships stable
- Drivers: default `resend`; also `ses`, `local`, postmark (planned), bird (planned), smtp (planned)
- Calls: `email.send({ to, template, data, idempotencyKey? })`, `email.marketing({ to, template, data, unsubscribe, idempotencyKey? })`, `email.raw()`, `email.templates.define(name, template)`, `email.templates.has(name)`, `email.suppression.create(address, opts?)`, `email.suppression.get(address)`, `email.suppression.delete(address)`, `email.unsubscribe.create(address, opts?)`, `email.unsubscribe.accept(token)`, `email.unsubscribe.handle(req)`, `email.ingest(body, provider?)`, `email.on(listener)`, `email.info()`, `createEmail(deps)`, `parseEmail(raw)`
- Docs: https://mainsdk.dev/docs/interfaces/email (Markdown: https://mainsdk.dev/docs/interfaces/email.md)
- Check codes: MAIN-EMAIL-0006, MAIN-EMAIL-0009, MAIN-EMAIL-0010, MAIN-EMAIL-0011, MAIN-CHECK-0026

### `mail`: Inbound mail and mailboxes

Receive email through your mail provider's webhook, keep it in mailboxes with threads and attachments, and pull people's existing mailboxes over IMAP or JMAP.

- Status: Building for 1.2 · ships experimental
- Drivers: no default; also `ses`, `postmark`, `mailgun`, `cloudflare`, `imap`, `jmap`, `local`
- Calls: `mail.on(route, fn)`, `mail.receive(req)`, `mail.list({ mailbox, folder?, thread?, unread?, cursor?, limit? })`, `mail.get(id)`, `mail.update(id, { folder?, read?, flagged?, labels? })`, `mail.thread(id)`, `mail.replyAddress({ thread, ttl? })`, `mail.headers({ replyTo })`, `mail.allowImages(id)`, `mail.pull(mailbox)`, `mail.connect(req, { provider, mailbox? })`, `mail.mailboxes()`
- Docs: https://mainsdk.dev/docs/interfaces/mail (Markdown: https://mainsdk.dev/docs/interfaces/mail.md)
- Check codes: MAIN-MAIL-0008, MAIN-MAIL-0009

### `realtime`: Realtime updates

Push live updates from your server to browsers over server-sent events, checked per channel and tenant.

- Status: Building for 0.3 · ships beta
- Drivers: default `broadcast`; also `local`, `pusher`, `ably`, `supabase-realtime`
- Calls: `realtime.publish(channel, event, data)`, `realtime.pipe(channel, tokens, { event? })`, `realtime.handler()`, `realtime.on(prefix, event, opts, fn)`, `realtime.onStream(prefix, fn)`, `realtime.ticket()`, `realtime.subscribe(channels, { onEvent, onReset? })`, `realtime.tokens(sub, channel, { onText, onDone })`, `http.websocket(url, options)`, `new SocketServer(hub)`, `new Realtime(ports)`, `parseRealtime(raw)`, `publishProgress(hub, source)`, `publishRecords(hub, source)`, `new Broadcaster(opts)`, `new MemoryBus()`, `redisBroadcast(o)`, `events(object: broadcast, onBroadcast)`
- Docs: https://mainsdk.dev/docs/interfaces/realtime (Markdown: https://mainsdk.dev/docs/interfaces/realtime.md)
- Check codes: MAIN-REALTIME-0003, MAIN-REALTIME-0006, MAIN-REALTIME-0007, MAIN-REALTIME-0012, MAIN-REALTIME-0015, MAIN-REALTIME-0016

### `feedback`: Feedback and support

Let users report a problem or open a ticket, with the trace attached and screenshots redacted.

- Status: Building for 0.6 · ships beta
- Drivers: default `builtin`; also `intercom`, `zendesk`, `plain`
- Calls: `feedback.open({ category? })`, `feedback.onCrash({ code? })`, `feedback.onFailedAction({ code })`, `support.ticket({ subject, body })`
- Docs: https://mainsdk.dev/docs/interfaces/feedback (Markdown: https://mainsdk.dev/docs/interfaces/feedback.md)

## Events, jobs and webhooks

### `webhooks`: Webhooks

Send signed webhooks with retries and verify incoming ones before your handler runs.

- Status: Preview for 0.3 · ships stable
- Drivers: default `builtin`; also `svix`
- Calls: `webhooks.emit()`, `webhooks.receive()`, `webhooks.delivery(id)`, `new Webhooks(inbound, outbound)`, `PRESETS(const)`, `devPaths(dir)`, `newSecret()`, `readDeliveries(dir, now)`, `replayKey(dir, create)`, `replaySignature(key, deliveryId, { body })`, `signHeaders(secrets, id, { timestampSeconds, body })`, `webhooks.handle(req, { remote? })`, `webhooks.send(type, data, { idempotencyKey? })`
- Docs: https://mainsdk.dev/docs/interfaces/webhooks (Markdown: https://mainsdk.dev/docs/interfaces/webhooks.md)
- Check codes: MAIN-HOOK-0011

### `workflows`: Durable workflows

Write a multi-step flow as one function that waits for days and resumes after a crash, with each finished step journaled and never run twice.

- Status: Preview for 0.3 · ships beta
- Drivers: default `db`; also `local`, `inngest`, `temporal`, `vercel-workflow`
- Calls: `workflow(name, opts, fn)`, `step(name, fn, opts?)`, `sleep(duration)`, `waitFor(type, opts)`, `approve(req)`, `workflows.start(wf, input, opts?)`, `workflows.get(runId)`, `workflows.list(opts?)`, `workflows.cancel(runId, opts)`, `new Workflows(opts)`, `workflows.register(workflow)`
- Docs: https://mainsdk.dev/docs/interfaces/workflows (Markdown: https://mainsdk.dev/docs/interfaces/workflows.md)
- Check codes: MAIN-WORKFLOW-0009, MAIN-WORKFLOW-0010, MAIN-WORKFLOW-0011, MAIN-WORKFLOW-0012

### `jobs`: Events and background jobs

Publish events, fan small messages out to every instance, and run background jobs that retry, survive restarts and never run twice for the same key.

- Status: Building for 0.3 · ships stable
- Drivers: default `db`; also `local`, `inngest`, `vercel-queues`, trigger-dev (planned), sqs (planned)
- Calls: `events.publish()`, `events.on()`, `events.broadcast(topic, payload)`, `events.onBroadcast(topic, fn)`, `jobs.run()`, `jobs.schedule()`, `new Events(opts)`, `typeMatches(pattern, type)`, `new Jobs(opts)`, `new WorkGate(pressure?)`, `nextCron(c, after)`, `parseCron(expr)`, `jobs.on(name, handler)`, `jobs.get(id)`, `jobs.dead.list({ cursor?, limit? })`, `jobs.dead.retry(id)`, `jobs.idle()`
- Docs: https://mainsdk.dev/docs/interfaces/jobs (Markdown: https://mainsdk.dev/docs/interfaces/jobs.md)
- Check codes: MAIN-EVENT-0004, MAIN-EVENT-0010, MAIN-EVENT-0011

## Files, sharing and media

### `files`: File uploads

Upload files to your own bucket, Uploadthing, Vercel Blob or getqr.id, scanned and private to their owner, with short-lived links to read them.

- Status: Building for 0.3 · ships stable
- Drivers: default `getqr`; also `s3`, `uploadthing`, `vercel-blob`, `local`
- Calls: `files.upload()`, `files.uploadUrl()`, `files.complete(id, { owner })`, `files.url()`, `files.get(id)`, `handleFilesRoute(files, req)`, `signatureScan(bytes)`, `files.accept(idOrKey, token, { bytes, type, signal? })`, `files.checkReadUrl(url)`, `files.read(path)`, `files.scanResult(id, verdict)`, `files.sign(s)`, `files.verify(s, sig)`, `files.delete(ref, { owner })`, `files.driver`
- Docs: https://mainsdk.dev/docs/interfaces/files (Markdown: https://mainsdk.dev/docs/interfaces/files.md)
- Check codes: MAIN-FILES-0010, MAIN-FILES-0016

### `share`: Share links and QR codes

Share any record, file or screen as a private, expiring getqr.id link and QR code, run offline QR placements, and get first-party open counts with consent; the only way an app hands out a link.

- Status: Building for 0.3 · ships stable
- Drivers: default `getqr`; also `local`
- Calls: `share(resource, { expiresIn })`, `share.get(id)`, `share.list()`, `share.update(id, { title?, url?, uploadId?, extend? })`, `share.revoke()`, `share.qr(id, { format?, size? })`, `share.story(id)`, `share.stats()`, `share.usage()`, `share.campaigns.create()`, `share.campaigns.get(id)`, `share.placements.create(campaignId, rows)`, `share.placements.list(campaignId, { consent })`, `share.placements.get(id, { consent })`, `share.placements.update(id)`, `share.on(type, fn)`, `share.webhooks.list()`, `share.webhooks.create(url, events)`, `GETQR_EVENT_TYPES(const)`, `GETQR_OPERATIONS(const)`, `new LocalGetqr(o?)`, `httpTransport(o)`, `localGetqrFetch(local)`, `createShare(o)`, `handleShareRoute(share, req)`, `verifyGetqrSignature(raw, header, { secret, nowMs })`, `withShareMembers(routed, base)`
- Docs: https://mainsdk.dev/docs/interfaces/share (Markdown: https://mainsdk.dev/docs/interfaces/share.md)
- Check codes: MAIN-SHARE-0001, MAIN-SHARE-0007, MAIN-CONTRACT-0001, MAIN-CONTRACT-0003, MAIN-CONTRACT-0004, MAIN-CONTRACT-0021, MAIN-CONTRACT-0030, MAIN-CONTRACT-0031

### `links`: Invite, deep and sign-in links

Send invite links, deep links that open the right screen on the web, Android and iOS, and sign-in links that hand off to auth, all minted by getqr.id through share.

- Status: Building for 0.4 · ships beta
- Drivers: default `getqr`; also `local`
- Calls: `links.create(route)`, `links.invite(route, { email?, uses?, expiresIn? })`, `links.accept(req, token)`, `links.signIn({ email } | { phone }, { next? })`, `links.resolve(url)`, `links.revoke(id)`, `createLinks(o)`, `parseLinks(raw)`, `links.route(req)`
- Docs: https://mainsdk.dev/docs/interfaces/links (Markdown: https://mainsdk.dev/docs/interfaces/links.md)
- Check codes: MAIN-LINK-0003, MAIN-LINK-0004, MAIN-LINK-0005

### `media`: Images, video and audio

Resize images to AVIF and WebP and transcode video and audio to HLS as workflows, with output through files and metadata stripped by default.

- Status: Building for 0.4 · ships beta
- Drivers: default `getqr`; also `mux`, `cloudinary`, `ffmpeg`, `local`
- Calls: `media.image(file, { widths?, formats? })`, `media.video(file, { renditions? })`, `media.audio(file, { bitrates? })`, `media.get(jobId)`, `media.cancel(jobId, { reason })`, `media.url(output, { for })`, `media.playlist(job, output, { for })`
- Docs: https://mainsdk.dev/docs/interfaces/media (Markdown: https://mainsdk.dev/docs/interfaces/media.md)
- Check codes: MAIN-MEDIA-0007, MAIN-MEDIA-0010, MAIN-MEDIA-0011, MAIN-MEDIA-0012, MAIN-MEDIA-0013

### `pdf`: PDF documents

Render PDFs from templates in the reader's language; the rendering half of documents.

- Status: Preview for 0.4 · ships beta
- Drivers: default `builtin`; also `docraptor`, `gotenberg`, documenso (planned)
- Calls: `pdf.render(template, data)`
- Docs: https://mainsdk.dev/docs/interfaces/pdf (Markdown: https://mainsdk.dev/docs/interfaces/pdf.md)
- Check codes: MAIN-DOC-0011

### `documents`: Document reading

Read PDFs, Office files and scans into text, tables and layout, with OCR from the driver you pick and active content never run.

- Status: Preview for 0.4 · ships beta
- Drivers: default `builtin`; also `textract`, `azure-document`, `google-document`, `unstructured`, `ai`
- Calls: `documents.parse(file, opts)`, `documents.start(file, opts)`, `documents.get(jobId)`, `documents.render(layout, data, opts)`, `new Documents(opts)`, `aiOcrDriver(opts)`, `azureDriver(o)`, `fakeOcrDriver(opts)`
- Docs: https://mainsdk.dev/docs/interfaces/documents (Markdown: https://mainsdk.dev/docs/interfaces/documents.md)
- Check codes: MAIN-DOC-0010

## Payments and plans

### `pay`: Payments

Take payments and subscriptions on the provider's hosted checkout, routed by market, with no card data in your app.

- Status: Building for 0.3 · ships stable
- Drivers: default `stripe`; also `razorpay`, paddle (planned), `local`
- Calls: `pay.checkout()`, `pay.subscribe()`, `pay.refund()`, `pay.on()`, `pay.agent({ agent, amount, to })`, `createPay(opts)`, `fromMicros(micros)`, `toMicros(value)`, `parseRate(value)`, `pay.handleWebhook(rawBody, headers)`, `pay.reportUsage(event)`, `pay.providerFor(market)`, `razorpay(opts)`
- Docs: https://mainsdk.dev/docs/interfaces/pay (Markdown: https://mainsdk.dev/docs/interfaces/pay.md)
- Check codes: MAIN-PAY-0001

### `meter`: Usage metering

Record what each tenant uses, idempotently and without slowing a request, read it back by window, and export it to pay for usage billing.

- Status: Building for 0.3 · ships beta
- Drivers: default `db`; also `redis`, `local`, `stripe-meters`, `lago`
- Calls: `meter.record(metric, value?, { feature?, apikey?, user?, at?, idempotencyKey? })`, `meter.usage({ metric, window, tenant?, groupBy? })`, `meter.flush()`, `localMeter(o)`, `meterIssues(raw, opts?)`, `parseMeter(raw)`, `meter.close()`, `meter.erase(user)`, `meter.exportDue(tenants)`, `meter.exportUser(user)`, `meter.pseudonym(user)`, `meter.startExport(list)`
- Docs: https://mainsdk.dev/docs/interfaces/meter (Markdown: https://mainsdk.dev/docs/interfaces/meter.md)
- Check codes: MAIN-METER-0001, MAIN-METER-0006

### `entitlements`: Plans and entitlements

Say what each plan includes, check it in one line, and set a tenant's plan from its subscription.

- Status: Building for 0.3 · ships beta
- Drivers: default `db`; also `local`
- Calls: `entitlements.has(feature)`, `entitlements.require(feature)`, `entitlements.limit(name)`, `entitlements.within(name, add?, fn?)`, `entitlements.plan()`, `entitlements.set(tenantId, plan, { reason, trial?, until? })`, `entitlements.grant(tenantId, { features?, limits? }, { reason, until? })`, `entitlements.revoke(tenantId, grantId, { reason })`, `entitlements.ungrant(tenantId, grantId, { reason })`, `new Entitlements(o)`, `PLAN_DDL(const)`, `dbPlanStore(o)`, `localPlanStore()`, `parsePlans(plans, ent, { meter? })`, `planIssues(plans, ent, { meter? })`
- Docs: https://mainsdk.dev/docs/interfaces/entitlements (Markdown: https://mainsdk.dev/docs/interfaces/entitlements.md)
- Check codes: MAIN-PLAN-0006, MAIN-PLAN-0010

### `licence`: App licensing

Sell your app with signed licence files that switch features on, verify offline against a pinned key, and keep serving through a grace period.

- Status: Building for 0.4 · ships experimental
- Drivers: default `offline`; also `local`, `http`
- Calls: `licence.state()`, `licence.has(feature)`, `licence.require(feature)`, `licence.limit(name)`, `licence.get()`, `licence.update(token, { scope? })`, `licence.refresh()`, `licence.machine()`, `licence.watch(scope, fn)`, `new AppLicence(opts)`, `storeKv(store)`, `parseLicenceConfig(raw)`, `licenceIssues(raw)`, `createTestLicenceCA(opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/licence (Markdown: https://mainsdk.dev/docs/interfaces/licence.md)
- Check codes: MAIN-LICENCE-0011, MAIN-LICENCE-0012, MAIN-LICENCE-0014

## Security and privacy

### `moderate`: Moderation

Take reports, review them in a queue, decide and take appeals with a statement of reasons, and hold uploads until a pre-screen clears them.

- Status: Building for 0.4 · ships experimental
- Drivers: default `builtin`; also `ai-classify`, `pdq`, `photodna`
- Calls: `moderate.report({ target, reason, by })`, `moderate.screen({ target, author, text?, files? })`, `moderate.state(target)`, `moderate.queue.list()`, `moderate.queue.assign(caseId, to)`, `moderate.decide(caseId, { action, ground, facts, by })`, `moderate.appeal(decisionId, { by, note })`, `moderate.resolve(appealId, { outcome, note, by })`, `moderate.statement(decisionId)`, `moderate.transparency({ from, to })`, `moderate.watch("decision", fn)`, `moderate.watch("match", fn)`, `sanitize(object: html, markdown, text, attr, url)`
- Docs: https://mainsdk.dev/docs/interfaces/moderate (Markdown: https://mainsdk.dev/docs/interfaces/moderate.md)
- Check codes: MAIN-MOD-0010, MAIN-MOD-0011, MAIN-MOD-0012, MAIN-MOD-0013, MAIN-SANITIZE-0003

### `secrets`: Secrets

Read provider keys on the server from your secrets manager, never from code or the browser.

- Status: Building for 0.2 · ships stable
- Drivers: default `vault`; also `aws-secrets-manager`, `gcp-secret-manager`, `azure-key-vault`, `doppler`, `infisical`, `env`, `local`
- Calls: `secrets.get()`, `secrets.raw()`, `resolveSecret(ref, source?)`, `secrets.all(name)`, `secrets.age(name)`, `secrets.watch(name, fn)`
- Docs: https://mainsdk.dev/docs/interfaces/secrets (Markdown: https://mainsdk.dev/docs/interfaces/secrets.md)
- Check codes: MAIN-SECRET-0001, MAIN-SECRET-0002, MAIN-SECRET-0003, MAIN-SECRET-0004, MAIN-SECRET-0011, MAIN-SECRET-0014, MAIN-CONFIG-0004, MAIN-CONFIG-0005, MAIN-CONFIG-0010

### `keys`: Encryption keys

Encrypt records with their own data keys, wrapped by a regional KMS key, with per-user keys for erasure.

- Status: Building for 0.3 · ships beta
- Drivers: default `aws-kms`; also `gcp-kms`, `azure-keyvault`, `vault-transit`, `local`
- Calls: `keys.encrypt()`, `keys.decrypt()`, `keys.forUser()`, `keys.destroyUser()`, `keys.rotate()`, `keys.sign({ profile })`, `keys.verify({ profile, publicKey, data, signature })`, `new Keys(opts)`, `new LocalKms(master)`, `new MemoryKeyStore()`, `keysFromConfig(cfg, deps)`, `keys.forTenant(tenant, { region? })`, `keys.destroyTenant(tenant)`, `keys.rewrap(sealed, { purpose })`, `keys.raw()`
- Docs: https://mainsdk.dev/docs/interfaces/keys (Markdown: https://mainsdk.dev/docs/interfaces/keys.md)
- Check codes: MAIN-KEYS-0001, MAIN-KEYS-0021, MAIN-KEYS-0023

### `crypto`: Cryptography

Encrypt fields, sign and verify, hash passwords and make tokens with modern algorithms only.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`; also `cloud-kms`, `vault-transit`, `libsodium`
- Calls: `crypto.keys.customer(org, { kms })`, `cryptoApi(object: every crypto helper under one name)`, `crypto.token(bytes?)`, `crypto.sign(privateKey, data)`, `crypto.verify(publicKey, { data, signature })`, `crypto.seal(key, data, { aad? })`, `crypto.open(key, sealed, { aad? })`, `crypto.generateKey()`, `crypto.deriveKey(secret, info)`, `crypto.randomBytes(n)`, `crypto.hash(data, alg?)`, `crypto.hashHex(data, alg?)`, `crypto.hmac(key, data, alg?)`, `crypto.verifyHmac(key, data, tag, alg?)`, `crypto.generateSigningKeyPair()`, `crypto.equal(a, b)`, `crypto.b64u(bytes)`, `crypto.fromB64u(text)`, `crypto.toHex(bytes)`
- Docs: https://mainsdk.dev/docs/interfaces/crypto (Markdown: https://mainsdk.dev/docs/interfaces/crypto.md)
- Check codes: MAIN-CRYPTO-0002, MAIN-CRYPTO-0013

### `e2e`: End-to-end encryption

Encrypt messages and records on people's devices with keys only they hold, using MLS for groups, so the server can't read them.

- Status: Building for 1.2 · ships experimental
- Drivers: default `mls`; also `local`
- Calls: `e2e.join()`, `e2e.group.create({ members })`, `e2e.group.add(groupId, member)`, `e2e.group.remove(groupId, member)`, `e2e.group.get(groupId)`, `e2e.encrypt(groupId, data)`, `e2e.decrypt(groupId, message)`, `e2e.recovery.enrol(prf)`, `e2e.recovery.restore(prf)`
- Docs: https://mainsdk.dev/docs/interfaces/e2e (Markdown: https://mainsdk.dev/docs/interfaces/e2e.md)
- Check codes: MAIN-E2E-0007, MAIN-E2E-0008, MAIN-E2E-0009

### `sandbox`: Code sandboxes

Run untrusted or AI-written code in a sandbox with time, memory and cost limits, no secrets, and only the hosts you allow.

- Status: Building for 0.4 · ships beta
- Drivers: default `vercel-sandbox`; also `e2b`, `local`
- Calls: `sandbox.run({ code, language, files, network, feature })`, `sandbox.create(opts)`, `sandbox.get(id)`, `sandbox.action({ name, intent, languages })`, `new Sandbox(deps)`, `createSandbox(deps)`, `parseSandboxConfig(raw)`, `secret(name)`, `localSandboxDriver(opts)`, `e2bNetworkPolicy(hosts)`, `vercelNetworkPolicy(hosts)`
- Docs: https://mainsdk.dev/docs/interfaces/sandbox (Markdown: https://mainsdk.dev/docs/interfaces/sandbox.md)
- Check codes: MAIN-SANDBOX-0010, MAIN-SANDBOX-0011, MAIN-SANDBOX-0012

### `limits`: Rate limits and quotas

Rate-limit requests, enforce quotas and take locks from named policies, with standard 429 answers and a declared mode for when the store is down.

- Status: Building for 0.3 · ships beta
- Drivers: default `local`; also `redis`, `arcjet`
- Calls: `limits.rate(key, policy, { cost? })`, `limits.enforce(policy, { key?, cost? })`, `limits.quota(name, { key?, add? })`, `limits.release(name, { key?, add })`, `limits.lock(name, fn, { ttl?, wait? })`, `limits.concurrency(name, fn, { key?, wait? })`, `limits.headers(decision)`, `new LimitError(code, message, retryAfter, headers, decision?)`, `asKeyLimits(limiter)`, `limitsIssues(raw)`, `localLimits(o?)`, `parseLimits(raw)`, `new StoreHub(o)`, `dbStore(o)`, `localStore(o?)`, `redisStore(o)`, `limits.hubs()`, `limits.info()`, `limits.switchStore(down)`, `limits.declaredRate(policy)`
- Docs: https://mainsdk.dev/docs/interfaces/limits (Markdown: https://mainsdk.dev/docs/interfaces/limits.md)
- Check codes: MAIN-LIMIT-0006, MAIN-LIMIT-0007, MAIN-LIMIT-0008

### `protect`: Protected files

Protect a file so it opens only under its policy, at its release time, can be revoked in seconds and shows who opened it.

- Status: Building for 0.4 · ships experimental
- Drivers: default `local`; also `taps-node`, `taps-hosted`, `sovedocs`
- Calls: `protect(file, { label, policy })`, `protect.get(asset)`, `protect.list({ cursor })`, `protect.update(asset, { policy, label })`, `protect.versions.create(asset, file)`, `protect.revoke(asset, { reason, person })`, `protect.erase(asset, { reason, approvals })`, `protect.transfer(asset, { to })`, `protect.activity(asset)`, `protect.usage(asset)`, `protect.open(asset, { purpose })`, `protect.policy.check(asset, { as, agent, purpose })`, `protect.policy.simulate(asset, policy)`, `protect.policy.lint(policy)`, `protect.zones.list()`, `protect.zones.update(zones, { approvals })`, `protect.threshold.get(asset)`, `protect.share(asset, { to })`, `protect.share.get(link)`, `protect.share.list(asset)`, `protect.share.update(link, change)`, `protect.share.revoke(link)`, `protect.share.stats(link, { consent })`, `protect.people.create(asset, { person })`, `protect.people.list(asset)`, `protect.people.remove(asset, person)`, `protect.agents(asset, { allowed })`, `protect.delegate.create({ agent, assets, actions, purposes, expiresIn })`, `protect.delegate.get(id)`, `protect.delegate.list()`, `protect.delegate.revoke(id)`, `protect.approvals.list({ state })`, `protect.approvals.get(id)`, `protect.approvals.update(id, { decision })`, `protect.query(assets, { question, purpose })`, `protect.mcp.tools()`, `protect.mcp.manifest()`, `protect.mcp.verify(manifest, tools)`, `protect.attest.agent(agent)`, `protect.attest.verify(evidence, { kind })`, `protect.inspect(asset, { function })`, `protect.environment.get()`, `protect.ledger.list({ asset })`, `protect.ledger.proof(asset)`, `protect.ledger.verify()`, `protect.ledger.export()`, `protect.leaks.create({ asset, evidence })`, `protect.leaks.trace(caseId, { approvals })`, `protect.lockdown.create({ reason, approvals })`, `protect.lockdown.delete(id, { approvals })`, `protect.channels.list()`, `protect.channels.events(pattern)`, `protect.channels.watch(pattern, fn)`, `protect.channels.security()`, `createProtect(deps)`, `new ProtectError(code, message, opts?)`, `PROTECT_CHANNELS(const)`, `PROTECT_DRIVERS(const)`, `localProtect(deps)`, `lintPolicy(policy)`, `parseProtectSettings(raw)`, `protectHosts(settings)`, `routeShare(base, built, { protect })`
- Docs: https://mainsdk.dev/docs/interfaces/protect (Markdown: https://mainsdk.dev/docs/interfaces/protect.md)
- Check codes: MAIN-PROTECT-0020, MAIN-PROTECT-0021, MAIN-PROTECT-0022

### `privacy`: Privacy and data rights

Export or erase a person's data across every driver with one call, and record their consent.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `privacy.export(user)`, `privacy.erase(user, { reason })`, `privacy.consent(user).set({ purpose, granted })`, `privacy.consent(user).get(purpose)`, `privacy.hold(user, { until, reason })`, `privacy.release(user)`, `privacy.consent(user).signal({ tcf } | { gpp })`, `privacy.register(name, erase, { export?, labels?, region? })`, `privacy.retain()`, `privacy.resume()`, `privacy.pending()`, `privacy.verify(record)`, `privacy.guard(user, { purpose, send })`, `privacy.registerRetainer(key, prune)`
- Docs: https://mainsdk.dev/docs/interfaces/privacy (Markdown: https://mainsdk.dev/docs/interfaces/privacy.md)
- Check codes: MAIN-PRIV-0001, MAIN-PRIV-0004

### `security`: Security events

Write security events in OCSF with ATT&CK tags, detect brute force, stuffing and probing in the app, and stream them to your own SIEM.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `security.emit({ type, severity, message, attrs?, attack? })`, `security.on(type, fn)`
- Docs: https://mainsdk.dev/docs/interfaces/security (Markdown: https://mainsdk.dev/docs/interfaces/security.md)
- Check codes: MAIN-SECURITY-0001, MAIN-SECURITY-0002

### `incident`: Incident controls

Switch off one action, route, interface, driver or agent, put the app in read-only mode, or sign everyone out, in seconds and without a deploy.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `incident.status()`, `incident.set(req, change, { reason, expires? })`
- Docs: https://mainsdk.dev/docs/interfaces/incident (Markdown: https://mainsdk.dev/docs/interfaces/incident.md)
- Check codes: MAIN-INCIDENT-0004, MAIN-INCIDENT-0007, MAIN-CONFIG-0020

### `canary`: Exfiltration guard and canaries

Stop a stolen secret or a bulk copy of personal data leaving your app, and plant records and keys that raise an alarm when an intruder touches them.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `canary.plant(model, { count? })`, `canary.plantAll()`, `canary.status()`
- Docs: https://mainsdk.dev/docs/interfaces/canary (Markdown: https://mainsdk.dev/docs/interfaces/canary.md)
- Check codes: MAIN-EXFIL-0006, MAIN-CANARY-0003

### `deps`: Dependency checks

Catch made-up, brand-new and look-alike packages, known advisories, denied licences and unreviewed install scripts in your dependencies when you run mainsdk check, offline-friendly and never in the app.

- Status: Building for 0.5 · ships beta
- Drivers: default `osv`; also `npm-registry`
- Calls: `mainsdk check`
- Docs: https://mainsdk.dev/docs/interfaces/deps (Markdown: https://mainsdk.dev/docs/interfaces/deps.md)
- Check codes: MAIN-DEPS-0001, MAIN-DEPS-0002, MAIN-DEPS-0003, MAIN-DEPS-0004, MAIN-DEPS-0005, MAIN-DEPS-0006, MAIN-DEPS-0007, MAIN-DEPS-0008

### `bots`: Bot defence

Score the sign-in, sign-up, reset and invite doors in process, challenge or refuse bots with a proof-of-work, and stop SMS and WhatsApp pumping before a message is paid for.

- Status: Building for 0.3 · ships beta
- Drivers: default `pow`; also `local`, `turnstile`, `hcaptcha`
- Calls: `bots.formToken()`, `bots.formFields()`, `bots.check(req, { door? })`, `bots.guardSend(phone)`, `bots.challenge({ score? })`
- Docs: https://mainsdk.dev/docs/interfaces/bots (Markdown: https://mainsdk.dev/docs/interfaces/bots.md)
- Check codes: MAIN-BOT-0005

### `connectors`: Connectors to GRC, privacy, vulnerability and ticket tools

Push assure evidence to your GRC tool, run privacy requests from Transcend or OneTrust, send SBOM and VEX to your vulnerability tool, and open one ticket per failure.

- Status: Building for 1.2 · ships beta
- Drivers: no default; also `servicenow-irm`, `archer`, `onetrust-grc`, `transcend`, `onetrust-privacy`, `dependency-track`, `snyk`, `wiz`, `defectdojo`, `github`, `jira`, `local`
- Calls: `connectorFor({ kind, settings }, ports)`, `connectorsFrom(config, ports)`, `pushAssure(connectors, run, options)`, `failuresOf(run, { owner, now })`, `syncTickets(tickets, run, options)`, `pushRelease(vulns, release, options)`, `importFindings(vulns, options)`, `processPrivacy(privacy, { steps, sign }, options)`, `testConnectors(connectors, options)`
- Docs: https://mainsdk.dev/docs/interfaces/connectors (Markdown: https://mainsdk.dev/docs/interfaces/connectors.md)
- Check codes: MAIN-CONNECTOR-0001

## Platform, OS and network

### `network`: Network permissions

Allow outbound calls only to the hosts you and your drivers declare, and block everything else.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `http.fetch(url, init)`, `http.get(url, init)`, `http.post(url, body, init)`, `http.json(url, init)`, `http.events(url, init)`, `http.websocket(url, protocols)`, `http.assert(url)`, `network.destinations()`, `exportAwsSg(allow, opts)`, `exportK8s(allow, opts)`, `isPrivateIp(ip)`, `parseRule(text)`, `createHttp(net)`, `network.assert(target)`, `network.mode`, `network.rules`, `network.decisions`, `network.entries`, `network.breakerSlots()`, `network.breakerStates()`, `network.decide(url, reason?, caller?)`, `network.decidePublic(url, caller?)`, `network.fetch(input, init?)`, `network.fetchPublic(input, init?)`, `network.matchHost(host)`, `network.matchIp(ip)`, `network.reload(opts)`, `network.settingsFor(host, call?)`, `network.trim()`
- Docs: https://mainsdk.dev/docs/interfaces/network (Markdown: https://mainsdk.dev/docs/interfaces/network.md)
- Check codes: MAIN-NET-0001, MAIN-NET-0004, MAIN-NET-0007, MAIN-NET-0040, MAIN-NET-0041, MAIN-NET-0050, MAIN-NET-0051

### `disk`: Local files

Read and write only the folders your server app declares; user uploads go through files, not here.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `disk.read(path)`, `disk.readText(path)`, `disk.write(path, data)`, `disk.list(path)`, `disk.exists(path)`, `disk.delete(path)`, `createDisk(perm)`, `validateDisk(disk)`, `disk.temp(fn, { prefix? })`, `disk.remove(path)`, `disk.space(path)`, `disk.usage(path)`
- Docs: https://mainsdk.dev/docs/interfaces/disk (Markdown: https://mainsdk.dev/docs/interfaces/disk.md)
- Check codes: MAIN-PERM-0001, MAIN-PERM-0003, MAIN-PERM-0008

### `system`: Server facts and programs

Read the facts about the server and run the programs you declare, with every URL they are given checked against the network list.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `system.user()`, `system.hostname()`, `system.platform()`, `system.networkInterfaces()`, `system.run(cmd, args, opts)`, `createSystem(perm)`, `SYSTEM_FACTS(const)`
- Docs: https://mainsdk.dev/docs/interfaces/system (Markdown: https://mainsdk.dev/docs/interfaces/system.md)
- Check codes: MAIN-PERM-0001, MAIN-PERM-0003, MAIN-NET-0001

### `net`: TCP, UDP and unix sockets

Open TCP connections, run TCP servers, send UDP datagrams and use unix domain sockets, each checked against the protocols, ports and directions main.yaml declares.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `net.connect(host, port, opts)`, `net.listen(port, handler, opts)`, `net.udp(opts)`, `net.connectUnix(path, opts)`, `net.listenUnix(path, handler, opts)`, `createNet(get)`
- Docs: https://mainsdk.dev/docs/interfaces/net (Markdown: https://mainsdk.dev/docs/interfaces/net.md)
- Check codes: MAIN-NET-0001, MAIN-NET-0007, MAIN-NET-0020, MAIN-NET-0025, MAIN-NET-0028, MAIN-NET-0029, MAIN-NET-0030

### `workers`: Worker threads and cluster

Run CPU-heavy work on a pool of worker threads or processes, and run one server instance per core, declared in main.yaml and carrying the tenant.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `workers.run(module, input, opts)`, `workers.snapshot()`, `worker(fn)`, `cluster.run(main)`, `cluster.restart(opts)`, `cluster.snapshot()`, `cluster.role()`, `cluster(object: one instance per core)`, `parseWorkers(section, ctx)`, `validateWorkers(config, ctx)`
- Docs: https://mainsdk.dev/docs/interfaces/workers (Markdown: https://mainsdk.dev/docs/interfaces/workers.md)
- Check codes: MAIN-WORKER-0001, MAIN-WORKER-0009, MAIN-WORKER-0010, MAIN-WORKER-0016

### `i18n`: Languages and formats

Translate messages and format money, dates and numbers for each locale, with right-to-left and Indian scripts.

- Status: Preview for 0.4 · ships beta
- Drivers: default `files`; also `tolgee`, `crowdin`, `lokalise`, `ai-draft`
- Calls: `t(key, values)`, `i18n.locale()`, `i18n.negotiate(req, { user })`, `i18n.with(locale, fn)`, `i18n.format.number(n, opts)`, `i18n.format.money(amount, currency)`, `i18n.format.date(d, opts)`, `i18n.format.list(items, opts)`, `i18n.format.relative(d)`, `i18n.dir(locale)`, `i18n.drafts.list({ locale })`, `i18n.drafts.approve(id, { text })`, `i18n.drafts.reject(id, { reason })`, `new Catalog(cfg, messages?)`, `new Drafts(o)`, `FORMATS(const)`, `I18N_DRIVERS(const)`, `new I18n(o)`, `NEGOTIATE_SOURCES(const)`, `PSEUDO_LOCALES(const)`, `checkMessages(o)`, `compileMessage(src)`, `createI18n(o)`, `crowdinSource(o)`, `detectSyntax(src)`, `exportBundle(format, b, opts?)`, `extractKeys(source, file)`, `fileCache(file)`, `fileDraftStore(dir)`, `formatMessage(m, ctx)`, `formatsFor(locale)`, `icuToMf2(src)`, `importBundle(format, text)`, `lastGood(source, cache, { log })`, `localeDir(locale)`, `localeSupported(locale)`, `lokaliseSource(o)`, `matchLocale(tag, locales)`, `memoryDraftStore()`, `negotiate(o, req, ctx?)`, `parseAcceptLanguage(header)`, `parseI18nConfig(raw)`, `parseIcu(src)`, `parseMf2(src)`, `printMf2(m)`, `pseudoMessage(src, locale)`, `pseudoMessages(messages, locale)`, `readGlossary(file)`, `readLocales(dir)`, `splitPlural(src)`, `tolgeeSource(o)`, `variablesOf(m)`, `writeLocale(dir, locale, { messages })`, `i18n.handle(req, next, { user? })`
- Docs: https://mainsdk.dev/docs/interfaces/i18n (Markdown: https://mainsdk.dev/docs/interfaces/i18n.md)
- Check codes: MAIN-I18N-0001, MAIN-I18N-0002, MAIN-I18N-0003, MAIN-I18N-0006, MAIN-I18N-0008

### `geo`: Location

Get a coarse location hint from the declared platform's headers and geocode addresses through network, never for access decisions.

- Status: Building for 0.7 · ships beta
- Drivers: default `platform-headers`; also `mapbox`, `google-maps`, `local`
- Calls: `geo.of(req)`, `geo.geocode(address, { country? })`, `createGeo(settings)`, `parseGeo(raw)`, `geoHosts(config)`, `geoEdge(config)`
- Docs: https://mainsdk.dev/docs/interfaces/geo (Markdown: https://mainsdk.dev/docs/interfaces/geo.md)
- Check codes: MAIN-GEO-0002, MAIN-GEO-0005

### `updates`: App updates and version blocking

Tell users an update is ready, block old versions on the server as well as in the app, and roll releases out by percentage and market.

- Status: Building for 0.7 · ships beta
- Drivers: default `local`; also `expo-updates`, `google-play`, `app-store`, `tauri`
- Calls: `updates.minVersion()`, `updates.check({ version, channel?, user?, market? })`, `updates.channel({ user?, market?, channel? })`, `updates.build()`, `updates.announce(build)`, `createUpdates(deps)`, `parseUpdates(raw)`, `updatesIssues(raw)`, `versionHeaders(version, platform?)`, `refusal(res)`, `applyUpdate(status, driver)`, `watchBuild({ build, onUpdate, banner? })`, `expoUpdates(module)`, `googlePlay(module)`, `appStore({ url, open })`, `tauri(module)`
- Docs: https://mainsdk.dev/docs/interfaces/updates (Markdown: https://mainsdk.dev/docs/interfaces/updates.md)
- Check codes: MAIN-UPDATE-0003, MAIN-UPDATE-0004

### `neighbours`: Neighbour apps

Let MainSDK apps on one machine hear each other's signals on user-only sockets and, between declared peers, ask for status, a raised posture or shed load, and nothing else.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `neighbours.ask(peer, verb, { to?, for?, reason? })`, `new Neighbours(opts)`, `parseNeighbours(section)`, `neighboursIssues(section, ctx)`, `rotateHostKey(appsDir)`
- Docs: https://mainsdk.dev/docs/interfaces/neighbours (Markdown: https://mainsdk.dev/docs/interfaces/neighbours.md)
- Check codes: MAIN-NEIGHBOUR-0001, MAIN-NEIGHBOUR-0005

### `apps`: App registry and link

See every MainSDK app on a machine from one local folder, and let two apps that declare each other prove who they are and call each other's exposed actions.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `apps.peer(id)`, `apps.call(id, action, { input?, signal? })`, `apps.provide(actions)`, `new AppRegistry(opts)`, `parseApps(section)`, `readRegistry(dir)`, `readSeal(doc)`
- Docs: https://mainsdk.dev/docs/interfaces/apps (Markdown: https://mainsdk.dev/docs/interfaces/apps.md)
- Check codes: MAIN-APPS-0004, MAIN-APPS-0005

### `device`: Connected devices

Optional plumbing for any fleet of devices or agents your app manages - enrolment, device identity and attestation, signed telemetry, policy sync, approved commands and health - with nothing loaded until you declare it.

- Status: Building for 1.3 · ships beta
- Drivers: default `local`; also `taps`
- Calls: `device.enrolments.create({ group, uses, expiresIn })`, `device.enrolments.list({ cursor })`, `device.enrolments.revoke(id, { reason })`, `device.get(id)`, `device.list({ group, state, cursor })`, `device.update(id, { name, group, labels })`, `device.revoke(id, { reason })`, `device.handler()`, `device.telemetry.on(stream, handler, { schema })`, `device.summary.get({ group })`, `device.policy.define(schema, { tighten })`, `device.policy.publish(content, { target, reason, rollout })`, `device.policy.get(version, { target })`, `device.policy.list({ target, cursor })`, `device.policy.rollback({ target, to, reason })`, `device.policy.tighten({ target, path, value, expiresIn, reason })`, `device.commands.define(type, { input, risk, approve, undo })`, `device.commands.create(type, { to, input, reason })`, `device.commands.get(id)`, `device.commands.list({ device, state, cursor })`, `device.commands.cancel(id, { reason })`, `device.commands.revert(id, { reason })`, `device.mcp.tools()`, `deviceClient({ url, keys, dir })`, `fileKeyStore(dir, { alg })`, `memoryKeyStore({ alg })`, `webCryptoKeyStore({ db })`, `softwareEvidence({ software })`, `simulateDevices(app, { count })`, `deviceConformance({ driver })`
- Docs: https://mainsdk.dev/docs/interfaces/device (Markdown: https://mainsdk.dev/docs/interfaces/device.md)
- Check codes: MAIN-DEVICE-0001, MAIN-DEVICE-0002, MAIN-DEVICE-0003, MAIN-DEVICE-0004, MAIN-DEVICE-0016, MAIN-DEVICE-0022, MAIN-DEVICE-0038, MAIN-DEVICE-0039, MAIN-DEVICE-0050, MAIN-DEVICE-0066, MAIN-DEVICE-0067, MAIN-DEVICE-0071, MAIN-DEVICE-0082, MAIN-DEVICE-0092

## Reliability and operations

### `backup`: Backups and restore drills

Declare how much data you can lose and how fast you must restore, and prove it with timed restore drills.

- Status: Building for 1.2 · ships beta
- Drivers: no default; also `local`, `neon-pitr`, `supabase-pitr`, pgbackrest (planned), `aws-backup`, `object-lock`
- Calls: `runDrill({ driver, target, profile, at })`, `signDrill(result, key)`, `verifyDrill(evidence, trusted)`, `backupIssues(profile, declared, drill)`, `drillControls({ standard, evidence, now })`, `backupBadge({ evidence, now })`
- Docs: https://mainsdk.dev/docs/interfaces/backup (Markdown: https://mainsdk.dev/docs/interfaces/backup.md)
- Check codes: MAIN-BACKUP-0001, MAIN-BACKUP-0002, MAIN-BACKUP-0003, MAIN-BACKUP-0006

### `deploy`: Deploy config and serverless schedules

Write the platform's own deploy files from main.yaml for Vercel, Cloudflare Workers, Fly.io and Docker, wake schedules through one tick route, check the build before it ships and diff two environments.

- Status: Building for 0.4 · ships beta
- Drivers: default `vercel`; also `cloudflare`, `fly`, `docker`
- Calls: `createTick(app)`, `tick.handle(req)`, `tick.scheduled(event)`, `deploySection(config)`, `staticSchedules(config)`, `slotOf(expr, now)`
- Docs: https://mainsdk.dev/docs/interfaces/deploy (Markdown: https://mainsdk.dev/docs/interfaces/deploy.md)
- Check codes: MAIN-DEPLOY-0001, MAIN-DEPLOY-0002, MAIN-DEPLOY-0003, MAIN-DEPLOY-0004, MAIN-DEPLOY-0005, MAIN-DEPLOY-0010, MAIN-DEPLOY-0011, MAIN-DEPLOY-0012

### `status`: Health, resources and status page

Serve health probes, drain cleanly on shutdown, track SLO burn, watch CPU, event-loop delay and memory. An app status page with incidents is planned for phase 2 and not built yet.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`; also `provider`
- Calls: `status.check(name, fn, { every?, timeout?, critical? })`, `resources.snapshot()`, `resources.state()`, `resources.onPressure(fn)`, `cgroupCores(read)`, `cgroupMemory(read)`, `isServerless(env)`, `nodeSources(opts)`, `parseResources(sec)`, `validateResources(sec)`, `parseHealth(sec, env)`, `validateHealth(sec, env, { known })`, `new Slo(opts)`, `parseSlo(sec)`, `validateSlo(sec)`, `resources(object: snapshot, state, onPressure)`, `realTimers(const)`
- Docs: https://mainsdk.dev/docs/interfaces/status-page (Markdown: https://mainsdk.dev/docs/interfaces/status-page.md)
- Check codes: MAIN-HEALTH-0005, MAIN-HEALTH-0006, MAIN-RESOURCE-0003

### `alerts`: Alerts

Route SLO burn, downed dependencies, security detections and your own alerts to email, Slack, PagerDuty, Opsgenie or a webhook, once.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `alerts.raise({ key, severity, title, attrs?, runbook? })`, `alerts.resolve(key)`, `alerts.ack(id)`, `alerts.get(id)`, `alerts.list({ state?, source?, cursor?, limit? })`
- Docs: https://mainsdk.dev/docs/interfaces/alerts (Markdown: https://mainsdk.dev/docs/interfaces/alerts.md)
- Check codes: MAIN-ALERT-0001, MAIN-ALERT-0002, MAIN-ALERT-0003

### `signals`: Signals

Say what the app noticed (an attack, CPU pressure, a downed dependency, a rotated key, an anomaly) as one typed, signed, short-lived signal that the app, its other instances and its neighbour apps hear.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `signals.emit(name, { severity, subject?, data?, ttl?, scope? })`, `signals.on(pattern, handler, { min?, from? })`, `signals.list({ name?, min?, live?, cursor?, limit? })`, `new Signals(opts)`, `parseSignals(section)`, `signalsIssues(section)`, `signalEnvelopeSchema(const: the JSON Schema of a signal)`
- Docs: https://mainsdk.dev/docs/interfaces/signals (Markdown: https://mainsdk.dev/docs/interfaces/signals.md)
- Check codes: MAIN-SIGNAL-0006, MAIN-SIGNAL-0007

### `playbooks`: Playbooks

Declare in main.yaml what the app does when a signal arrives, with dry-run, approval, an audit trail and automatic rollback; every step only tightens and expires.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `playbooks.list()`, `playbooks.plan(rule, { signals? })`, `playbooks.run(rule, { signals?, reason? })`, `playbooks.get(runId)`, `playbooks.revert(runId, { reason })`, `posture.get()`, `posture(object: the reaction posture)`, `new Playbooks(opts)`, `parseReaction(main, ctx)`, `reactionIssues(main, ctx)`
- Docs: https://mainsdk.dev/docs/interfaces/playbooks (Markdown: https://mainsdk.dev/docs/interfaces/playbooks.md)
- Check codes: MAIN-PLAYBOOK-0001, MAIN-PLAYBOOK-0002, MAIN-PLAYBOOK-0010, MAIN-GUARD-0008

## Observability and analytics

### `log`: Logs, audit and telemetry

Log with redaction built in, keep a tamper-evident audit log and export telemetry to any OpenTelemetry backend.

- Status: Building for 0.2 · ships stable
- Drivers: default `terminal`; also `otel`, `mainsdk`, `sentry`
- Calls: `log.info()`, `log.error()`, `log.audit()`, `log.audit.verify()`, `new MainError(code, { fix })`, `problem(err)`, `log.debug(msg, attrs?)`, `log.warn(msg, attrs?)`, `log.verifyAudit({ from? })`, `verifyChain(records)`, `currentTrace()`, `newTraceId()`, `withTrace(id, fn)`, `fileSink(path)`, `log.records`, `log.auditRecords`, `readAuditFile(path)`, `instance(object: id)`, `log.addRedactor(fn)`, `log.addSecret(value)`, `log.auditFor(tenant)`, `log.reload(opts)`, `log.holdsSecret(text)`, `log.redactData(attrs)`, `log.redactString(text)`, `log.secretValues()`, `log.errors.list(query?: { since?, release?, cursor?, limit? })`, `log.errors.get(fingerprint)`, `auditEvent(input)`, `parseErrorReportConfig(logging)`, `errorConfigIssues(logging, ctx)`, `reportErrors(opts?: { endpoint?, sample? })`
- Docs: https://mainsdk.dev/docs/interfaces/log (Markdown: https://mainsdk.dev/docs/interfaces/log.md)
- Check codes: MAIN-REPORT-0005, MAIN-LOG-0020

### `observe`: Observe the SDK

Subscribe to every decision and side effect the SDK makes on node:diagnostics_channel, with a bounded queue, redacted payloads and no cost when nobody listens.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `observe(pattern, fn, opts?)`, `observe.channels()`, `observe.schema(name)`, `observe.flush()`, `observe.stats()`, `observe.otel(api?)`, `registerChannel(def)`, `removeChannel(name, version)`, `detailScope(opts, fn)`, `tracedDriver(driver, { publisher, interface, ops })`, `app.observe(pattern, fn, opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/observe (Markdown: https://mainsdk.dev/docs/interfaces/observe.md)

### `analytics`: Product analytics

Track product events, metrics and usage, private by default, with nothing recorded before consent where it is required.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`; also `posthog`, `otel`, `plausible`
- Calls: `analytics.track(event, props?)`, `analytics.page(path, props?)`, `analytics.counter(name).add(n?, attrs?)`, `analytics.gauge(name).set(value, attrs?)`, `analytics.histogram(name).record(value, attrs?)`, `analytics.query(q)`
- Docs: https://mainsdk.dev/docs/interfaces/analytics (Markdown: https://mainsdk.dev/docs/interfaces/analytics.md)
- Check codes: MAIN-ANALYTICS-0010, MAIN-ANALYTICS-0011, MAIN-ANALYTICS-0012, MAIN-ANALYTICS-0013

### `norms`: Behaviour norms

Learn what normal looks like for the app (traffic, errors, latency, CPU, loop delay, memory) by hour of week, deterministically and with no model, and emit an anomaly signal when it leaves the range.

- Status: Building for 0.4 · ships beta
- Drivers: default `builtin`
- Calls: `norms.get(metric, { at? })`, `norms.list({ cursor?, limit? })`, `norms.delete(metric, { reason })`, `new Norms(opts)`, `parseNorms(section?)`, `normsIssues(section)`
- Docs: https://mainsdk.dev/docs/interfaces/norms (Markdown: https://mainsdk.dev/docs/interfaces/norms.md)
- Check codes: MAIN-NORMS-0001

## Developer experience

### `config`: Runtime settings

Change typed runtime settings without a deploy, with every change audited.

- Status: Building for 0.2 · ships stable
- Drivers: no default; also `files`, `local`, `redis`, `postgres`, `vercel-global-config`, `aws-appconfig`, `cloudflare-kv`
- Calls: `config.get(key)`, `config.watch(key, fn)`, `config.watch(prefix, fn)`, `config.refresh()`, `config.ready(opts)`, `config.raw()`, `redisConfig(o)`, `filesConfig(o)`, `new MemoryConfigStore(initial?)`, `loadMain(dir, env?)`, `parseMain(text, overlays?)`, `parseYaml(src)`, `schemaNode(schema, key)`, `config.close()`
- Docs: https://mainsdk.dev/docs/interfaces/config (Markdown: https://mainsdk.dev/docs/interfaces/config.md)
- Check codes: MAIN-CONFIG-0030, MAIN-CONFIG-0031, MAIN-CONFIG-0032, MAIN-CONFIG-0033

### `schema`: Schema and parsers

Describe app data once with s, get a strict check, a type and a JSON Schema, and turn raw text into checked values without a model.

- Status: Building for 0.2 · ships beta
- Drivers: default `builtin`
- Calls: `s.object(shape)`, `schema.parse(value)`, `schema.safeParse(value)`, `schema.toJSONSchema()`, `parse.text(raw)`, `parse.match(raw, list)`, `parse.regex(raw, re, schema)`, `parse.json(raw, schema)`, `parse.csv(raw, { schema })`, `parse.lines(raw)`, `parse.number(raw, { locale })`, `parse.date(raw)`, `parse.first(...parsers)`, `check(schema, value)`, `checkAsync(schema, value)`, `jsonSchemaOf(schema)`, `parse(object: text, match, regex, json, csv, lines, number, date, first, exact, pattern)`, `s(object: schema builders)`, `isUnsafeRegex(source)`, `parseDuration(v, key, { allowZero? })`, `parseSize(v)`
- Docs: https://mainsdk.dev/docs/interfaces/schema (Markdown: https://mainsdk.dev/docs/interfaces/schema.md)

### `flags`: Feature flags

Turn features on for some users, a market or a share of users, and change them while the app runs.

- Status: Preview for 0.3 · ships beta
- Drivers: default `config`; also `openfeature`, `posthog`, `vercel-flags`
- Calls: `flags.enabled(name, ctx)`, `flags.value(name, fallback, ctx)`
- Docs: https://mainsdk.dev/docs/interfaces/flags (Markdown: https://mainsdk.dev/docs/interfaces/flags.md)

### `api`: Typed API and OpenAPI document

Turn the app's actions into REST routes, one OpenAPI document and a typed client, with idempotent writes and breaking changes caught before release.

- Status: Building for 0.3 · ships beta
- Drivers: default `builtin`
- Calls: `api.openapi(actions, opts?)`, `api.routes(actions)`, `api.handler(options)`, `api.client(doc)`, `api.diff(before, after)`, `api.react(options)`, `createClient({ baseUrl, auth, fetch?, retries? })`, `page(rows, init)`, `pageLimit(limit, max?)`, `paginate(list, opts?)`, `inbound(object: handle)`, `inboundIssues(config, env, { today? })`, `originAllowed(origin, origins)`
- Docs: https://mainsdk.dev/docs/interfaces/api (Markdown: https://mainsdk.dev/docs/interfaces/api.md)
- Check codes: MAIN-API-0010, MAIN-API-0011, MAIN-API-0012, MAIN-API-0020

### `testing`: Testing kit

Test against in-memory fakes of every interface, control time and inject provider outages.

- Status: Building for 0.4 · ships stable
- Drivers: default `local`
- Calls: `createTestApp({ main })`, `createSim({ apps, start, seed })`, `sim.run(duration, { step })`, `sim.inject.cpu(app, curve)`, `sim.expect.signal(app, name)`, `sim.report({ golden })`, `app.fixtures(opts?)`, `mailbox(app).toHaveSent()`, `clock.advance()`, `chaos.on()`, `expect(app.pay).toHaveCharged()`, `assertFakesCover(ids)`, `runDriverKit(driver)`, `policyTest()`, `createApp(options)`, `configure(app)`, `app()`, `fakeAiDriver()`, `testCards()`, `new App(opts?)`
- Docs: https://mainsdk.dev/docs/interfaces/testing (Markdown: https://mainsdk.dev/docs/interfaces/testing.md)

### `adapters`: Framework adapters

One import wires MainSDK into Next.js, Hono or Express, with a request context, a trace, ordered hooks and problem-detail errors.

- Status: Building for 0.2 · ships beta
- Drivers: default `next`; also `hono`, `express`, `cloudflare`
- Calls: `mainsdk.proxy()`, `withMainSDK(handler)`, `mainsdkAction(handler)`, `mainsdk()`, `mainsdk.errors()`, `worker({ fetch })`, `worker(handler)`, `withPrisma(client)`, `withDrizzle(db)`
- Docs: https://mainsdk.dev/docs/interfaces/adapters (Markdown: https://mainsdk.dev/docs/interfaces/adapters.md)
- Check codes: MAIN-DATA-0001

### `upgrade`: Upgrades

Move every MainSDK package to a new release together with one command that applies the release's codemods and main.yaml moves and opens a pull request, and learn when a security release is out.

- Status: Building for 0.5 · ships beta
- Drivers: default `npm-registry`; also `git`
- Calls: `mainsdk upgrade`, `mainsdk upgrade --check`
- Docs: https://mainsdk.dev/docs/interfaces/upgrade (Markdown: https://mainsdk.dev/docs/interfaces/upgrade.md)
- Check codes: MAIN-UPGRADE-0002, MAIN-UPGRADE-0005, MAIN-UPGRADE-0010

### `adopt`: Adopt an existing app

Bring an existing Next.js or Node app, including one from Lovable, Bolt or v0, onto MainSDK one module and one pull request at a time, with its behaviour locked first and its coding agent driving the steps.

- Status: Building for 0.5 · ships beta
- Drivers: default `builtin`
- Calls: `mainsdk adopt scan`, `mainsdk adopt report`, `mainsdk adopt lock`, `mainsdk adopt next`, `mainsdk adopt finish`
- Docs: https://mainsdk.dev/docs/interfaces/adopt (Markdown: https://mainsdk.dev/docs/interfaces/adopt.md)
- Check codes: MAIN-ADOPT-0002, MAIN-ADOPT-0003, MAIN-ADOPT-0004, MAIN-ADOPT-0007
