Instance API
The Instance API manages the ainative-business process lifecycle on a self-hosted installation. It exposes instance configuration (branch name, data directory, guardrails), idempotent initialization, and the full upgrade detection + application flow. The upgrade system uses a background poller that compares the local branch against origin/main; when commits are available, the operator spawns an upgrade-assistant task that performs a guided git merge.
When running on the canonical ainative-business development repository (AINATIVE_DEV_MODE=true or the .git/ainative-dev-mode sentinel), all endpoints return safe synthetic responses so that dev-environment state never bleeds into production UIs.
Quick Start
Check for available upgrades, trigger the upgrade if one is found, and poll until the task completes:
// 1. Read current instance config and upgrade state
interface InstanceConfig {
branchName: string;
dataDir: string;
createdAt: string;
}
interface UpgradeState {
upgradeAvailable: boolean;
commitsBehind: number;
lastPolledAt: number | null;
lastUpgradeTaskId: string | null;
}
interface ConfigResponse {
devMode: boolean;
config: InstanceConfig | null;
guardrails: object | null;
upgrade: UpgradeState | null;
}
const { devMode, config, upgrade }: ConfigResponse =
await fetch('/api/instance/config').then((r: Response) => r.json());
if (devMode) {
console.log('Running in dev mode — upgrade flow disabled');
} else if (!config) {
// 2. Initialize if not yet bootstrapped
await fetch('/api/instance/init', { method: 'POST' });
console.log('Instance initialized');
} else {
console.log(`Branch: ${config.branchName}`);
console.log(`Upgrade available: ${upgrade?.upgradeAvailable}`);
console.log(`Commits behind: ${upgrade?.commitsBehind}`);
}
// 3. Force-check for new upstream commits
const { ok, state }: { ok: boolean; state?: UpgradeState } =
await fetch('/api/instance/upgrade/check', { method: 'POST' }).then((r: Response) => r.json());
if (ok && state?.upgradeAvailable) {
// 4. Spawn the upgrade task
const { taskId }: { taskId: string } =
await fetch('/api/instance/upgrade', { method: 'POST' }).then((r: Response) => r.json());
console.log(`Upgrade task spawned: ${taskId}`);
console.log('Navigate to the task view to watch streaming progress');
} Base URL
/api/instance
Endpoints
Get Instance Config
/api/instance/config Return the full instance state in a single response: config, guardrails, and upgrade state. Used by the Settings → Instance section and the upgrade pre-flight modal. In dev mode returns null payloads to prevent test state from surfacing in the UI.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| devMode | boolean | * | True when running on the canonical dev repo — all other fields will be null |
| config | object | null | * | Instance configuration record |
| config.branchName | string | — | Git branch this instance is tracking |
| config.dataDir | string | — | Data directory path |
| config.createdAt | ISO 8601 | — | Timestamp when the instance was first initialized |
| guardrails | object | null | * | Active guardrail settings (budget caps, tool restrictions) |
| upgrade | UpgradeState | null | * | Current upgrade state (see Upgrade State schema below) |
Read instance state to populate a settings screen or pre-flight modal:
// Read instance state — single round-trip for config + guardrails + upgrade
const res: Response = await fetch('/api/instance/config');
const { devMode, config, guardrails, upgrade }: {
devMode: boolean;
config: { branchName: string; dataDir: string; createdAt: string } | null;
guardrails: object | null;
upgrade: { upgradeAvailable: boolean; commitsBehind: number } | null;
} = await res.json();
if (devMode) {
console.log('Dev mode — instance config unavailable');
} else if (config) {
console.log(`Instance on branch: ${config.branchName}`);
console.log(`Data dir: ${config.dataDir}`);
if (upgrade?.upgradeAvailable) {
console.log(`${upgrade.commitsBehind} upstream commit(s) available`);
}
} Example response:
{
"devMode": false,
"config": {
"branchName": "instance/team-prod",
"dataDir": "/Users/team/.`ainative-business`",
"createdAt": "2026-03-01T09:00:00.000Z"
},
"guardrails": {
"maxBudgetUsd": 10.00,
"allowedRuntimes": ["claude-code", "anthropic-direct"]
},
"upgrade": {
"upgradeAvailable": true,
"commitsBehind": 4,
"lastPolledAt": 1744800000,
"lastSuccessfulUpgradeAt": 1743500000,
"lastUpgradeTaskId": null,
"pollFailureCount": 0,
"lastPollError": null
}
} Initialize Instance
/api/instance/init Idempotent manual re-run of the instance bootstrap. Useful when the initial boot-time run failed (permission error, git not installed), or when the user wants to re-apply guardrails after changing consent settings. Returns the refreshed instance state after re-running bootstrap.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| ensureResult | object | * | Bootstrap result from ensureInstance() |
| config | object | null | * | Instance configuration after bootstrap |
| guardrails | object | null | * | Guardrail settings after bootstrap |
| upgrade | object | null | * | Upgrade state after bootstrap |
Re-run bootstrap to recover from a failed initialization or re-apply changed consent settings:
// Re-run instance bootstrap — safe to call multiple times
const res: Response = await fetch('/api/instance/init', { method: 'POST' });
if (res.ok) {
const { ensureResult, config }: {
ensureResult: { created: boolean };
config: { branchName: string } | null;
} = await res.json();
if (ensureResult.created) {
console.log(`Instance bootstrapped on branch: ${config?.branchName}`);
} else {
console.log('Instance already initialized — guardrails re-applied');
}
} else {
const { error }: { error: string } = await res.json();
console.error(`Bootstrap failed: ${error}`);
} Errors: 500 — Bootstrap failure (check logs for permission or git errors)
Spawn Upgrade Task
/api/instance/upgrade Spawn an upgrade-assistant task that performs a guided git merge of upstream commits. Returns 202 Accepted with the task ID immediately — the merge runs asynchronously. Requires upgradeAvailable to be true. Rejects with 409 if the instance is not initialized or no upgrade is available.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| taskId | string (UUID) | * | ID of the spawned upgrade task — navigate to this task to watch progress |
The upgrade task uses the upgrade-assistant agent profile. The task description contains the branch name, number of commits behind, and data directory as context variables that the profile’s SKILL.md references. The task runs the standard merge flow: fetch upstream, merge, handle conflicts interactively, and rollback on any failure.
// Spawn the upgrade task — navigate to the task view for streaming progress
const res: Response = await fetch('/api/instance/upgrade', { method: 'POST' });
if (res.status === 202) {
const { taskId }: { taskId: string } = await res.json();
console.log(`Upgrade task: ${taskId}`);
// Navigate to /tasks/${taskId} to watch the merge progress
} else if (res.status === 409) {
const { error }: { error: string } = await res.json();
console.log(`Cannot upgrade: ${error}`);
} Example response:
{ "taskId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }Errors:
409— Instance not initialized (callPOST /api/instance/initfirst) orupgradeAvailableis false500— Internal failure persisting the task or upgrade state
Check for Upgrades
/api/instance/upgrade/check Force-run the upgrade availability poller. Rate-limited to one run per ~5 minutes via a shared lock file. Compares the local branch HEAD against origin/main using git rev-list. Returns the updated UpgradeState on success, or a skipped reason if the lock was held or dev mode was active.
Response Body (200 — poller ran)
| Field | Type | Req | Description |
|---|---|---|---|
| ok | boolean | * | True — poller ran successfully |
| state | UpgradeState | * | Fresh upgrade state after the poll |
Response Body (202 — poller skipped)
| Field | Type | Req | Description |
|---|---|---|---|
| ok | boolean | * | False — poller was skipped |
| skipped | string | — | Reason: "lock-held", "dev-mode", or "rate-limited" |
| error | string | — | Error message if the poller threw |
Trigger an immediate upgrade check — useful after the user clicks “Check now” in the settings UI:
// Force an upgrade check and display the result
interface UpgradeState {
upgradeAvailable: boolean;
commitsBehind: number;
lastPolledAt: number | null;
pollFailureCount: number;
}
const res: Response = await fetch('/api/instance/upgrade/check', { method: 'POST' });
const body: { ok: boolean; state?: UpgradeState; skipped?: string } = await res.json();
if (body.ok && body.state) {
if (body.state.upgradeAvailable) {
console.log(`Upgrade available — ${body.state.commitsBehind} commit(s) behind origin/main`);
} else {
console.log('Up to date');
}
} else {
console.log(`Check skipped: ${body.skipped ?? 'unknown'}`);
} Example responses:
{
"ok": true,
"state": {
"upgradeAvailable": true,
"commitsBehind": 4,
"lastPolledAt": 1744803600,
"lastSuccessfulUpgradeAt": 1743500000,
"lastUpgradeTaskId": null,
"pollFailureCount": 0,
"lastPollError": null
}
}{ "ok": false, "skipped": "rate-limited" }Errors: 500 — Unexpected poller failure
Get Upgrade Status
/api/instance/upgrade/status Return the current UpgradeState for client components that need to poll (e.g. the upgrade modal pre-flight). In dev mode returns a synthetic state with upgradeAvailable: false so the upgrade button never renders on the dev repo.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| devMode | boolean | * | True when running on the canonical dev repo |
| upgradeAvailable | boolean | * | Whether a new upstream version is available |
| commitsBehind | number | * | Number of commits local branch is behind origin/main |
| lastPolledAt | number | null | * | Unix timestamp of the last successful poll |
| lastSuccessfulUpgradeAt | number | null | * | Unix timestamp of the last applied upgrade |
| lastUpgradeTaskId | string | null | * | Task ID of the most recent upgrade task |
| pollFailureCount | number | * | Consecutive poll failures (resets to 0 on success) |
| lastPollError | string | null | * | Most recent poll error message, if any |
Poll upgrade status from a client component — e.g. to show a badge or refresh the pre-flight modal:
// Poll upgrade status from a client component
interface UpgradeStatus {
devMode: boolean;
upgradeAvailable: boolean;
commitsBehind: number;
lastPolledAt: number | null;
lastUpgradeTaskId: string | null;
pollFailureCount: number;
}
const status: UpgradeStatus = await fetch('/api/instance/upgrade/status')
.then((r: Response) => r.json());
if (status.devMode) {
console.log('Dev mode — upgrade status suppressed');
} else if (status.upgradeAvailable) {
console.log(`Upgrade available: ${status.commitsBehind} commits behind`);
if (status.lastUpgradeTaskId) {
console.log(`Previous upgrade task: ${status.lastUpgradeTaskId}`);
}
} else {
const lastPoll = status.lastPolledAt
? new Date(status.lastPolledAt * 1000).toLocaleString()
: 'never';
console.log(`Up to date. Last checked: ${lastPoll}`);
} Example response:
{
"devMode": false,
"upgradeAvailable": false,
"commitsBehind": 0,
"lastPolledAt": 1744803600,
"lastSuccessfulUpgradeAt": 1744800000,
"lastUpgradeTaskId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"pollFailureCount": 0,
"lastPollError": null
} Upgrade State Schema
| Field | Type | Description |
|---|---|---|
| upgradeAvailable | boolean | True when commitsBehind > 0 and last poll succeeded |
| commitsBehind | number | Commits local branch is behind origin/main |
| lastPolledAt | number | null | Unix timestamp of last successful git rev-list check |
| lastSuccessfulUpgradeAt | number | null | Unix timestamp of last completed upgrade |
| lastUpgradeTaskId | string | null | UUID of the most recently spawned upgrade task |
| pollFailureCount | number | Consecutive poll failures — resets to 0 on success |
| lastPollError | string | null | Error message from the most recent failed poll |
Dev Mode
When AINATIVE_DEV_MODE=true is set in the environment or the .git/ainative-dev-mode sentinel file is present, the Instance API operates in dev mode:
GET /api/instance/config— returns{ devMode: true, config: null, guardrails: null, upgrade: null }GET /api/instance/upgrade/status— returns a synthetic state withupgradeAvailable: falsePOST /api/instance/upgradeandPOST /api/instance/upgrade/check— are not blocked but return early without writing state
This prevents stale instance rows written during local testing from surfacing in production UIs when the dev repo is opened.