Settings API

Settings control every aspect of ainative-business’s behavior — from API key management and budget limits to model routing and runtime tuning. Each configuration domain lives under its own sub-route, so clients only fetch and update what they need.

Quick Start

Check provider status, configure an API key, and set a budget limit — a typical setup flow:

// 1. Get the current provider overview
const res1: Response = await fetch('/api/settings/providers');
const { providers, routingPreference }: { providers: Record<string, any>; routingPreference: string } = await res1.json();
console.log(`Anthropic configured: ${providers.anthropic.configured}`);
console.log(`Routing: ${routingPreference}`);

// 2. Set an Anthropic API key
await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: 'sk-ant-api03-...' }),
});

// 3. Set a daily and monthly budget limit
await fetch('/api/settings/budgets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dailyLimit: 10.00, monthlyLimit: 200.00 }),
});

// 4. Route to the cheapest available model
await fetch('/api/settings/routing', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ preference: 'cost' }),
});

// 5. Test the connection
const testRes: Response = await fetch('/api/settings/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ runtime: 'claude-code' }),
});
const test: { connected: boolean } = await testRes.json();
console.log(`Connected: ${test.connected}`);

Base URL

/api/settings

Provider Configuration

Get Provider Overview

GET /api/settings/providers

Aggregated provider status including configured runtimes, auth methods, dual-billing detection, and routing preference.

Response Body

FieldTypeReqDescription
providers.anthropic.configuredboolean*Whether any Anthropic runtime is configured
providers.anthropic.authMethodstring*Current auth method (oauth, api-key, etc.)
providers.anthropic.hasKeyboolean*Whether an API key is present
providers.anthropic.dualBillingboolean*True when both OAuth and API key are active
providers.anthropic.runtimesobject[]*Runtime setup states for Claude Code and Anthropic Direct
providers.openai.configuredboolean*Whether any OpenAI runtime is configured
providers.openai.hasKeyboolean*Whether an OpenAI API key is present
providers.openai.runtimesobject[]*Runtime setup states for Codex and OpenAI Direct
routingPreferenceenum*cost, latency, quality, or manual
configuredProviderCountnumber*Number of providers with at least one runtime configured

Check which providers are ready before assigning tasks to agents:

// Check provider readiness before creating tasks
const res: Response = await fetch('http://localhost:3000/api/settings/providers');
const { providers, routingPreference, configuredProviderCount }: {
providers: Record<string, any>;
routingPreference: string;
configuredProviderCount: number;
} = await res.json();

console.log(`${configuredProviderCount} provider(s) configured`);
console.log(`Anthropic: ${providers.anthropic.configured ? 'ready' : 'not configured'}`);
console.log(`OpenAI: ${providers.openai.configured ? 'ready' : 'not configured'}`);
console.log(`Routing: ${routingPreference}`);

// Warn about dual billing
if (providers.anthropic.dualBilling) {
console.warn('Both OAuth and API key active — may cause double billing');
}

Example response:

{
  "providers": {
    "anthropic": {
      "configured": true,
      "authMethod": "api-key",
      "hasKey": true,
      "dualBilling": false,
      "runtimes": [
        { "id": "claude-code", "name": "Claude Code", "status": "ready" },
        { "id": "anthropic-direct", "name": "Anthropic Direct", "status": "ready" }
      ]
    },
    "openai": {
      "configured": false,
      "hasKey": false,
      "runtimes": [
        { "id": "openai-codex-app-server", "name": "Codex", "status": "not_configured" }
      ]
    }
  },
  "routingPreference": "quality",
  "configuredProviderCount": 1
}

Get Anthropic Auth Settings

GET /api/settings

Read current Anthropic authentication settings (key presence, auth method, API key source).

Response Body

FieldTypeReqDescription
methodstring*Auth method (oauth, api-key)
hasKeyboolean*Whether an API key is stored
apiKeySourcestringKey origin (manual, oauth, env)
// Read current Anthropic auth settings
const res: Response = await fetch('http://localhost:3000/api/settings');
const auth: { method: string; hasKey: boolean; apiKeySource?: string } = await res.json();
console.log(`Auth method: ${auth.method}, key present: ${auth.hasKey}`);

Example response:

{
  "method": "api-key",
  "hasKey": true,
  "apiKeySource": "manual"
}

Update Anthropic Auth Settings

POST /api/settings

Update Anthropic authentication configuration. Validated with Zod.

Response 200 — Updated auth settings object

Set or rotate the Anthropic API key:

// Set or rotate the Anthropic API key
await fetch('http://localhost:3000/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: 'sk-ant-api03-...' }),
});

Errors: 400 — Zod validation failure

Get OpenAI Auth Settings

GET /api/settings/openai

Read current OpenAI authentication settings.

Response Body

FieldTypeReqDescription
hasKeyboolean*Whether an OpenAI API key is stored
apiKeySourcestringKey origin (manual, env)
// Read current OpenAI auth settings
const res: Response = await fetch('http://localhost:3000/api/settings/openai');
const openai: { hasKey: boolean; apiKeySource?: string } = await res.json();
console.log(`OpenAI key present: ${openai.hasKey}`);

Update OpenAI Auth Settings

POST /api/settings/openai

Update OpenAI authentication configuration. Validated with Zod.

Response 200 — Updated OpenAI auth settings object

// Configure OpenAI for Codex runtime access
await fetch('http://localhost:3000/api/settings/openai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: 'sk-proj-...' }),
});

Errors: 400 — Zod validation failure

Budget Guardrails

Get Budget Snapshot

GET /api/settings/budgets

Retrieve the current budget guardrail snapshot including limits and spend-to-date.

Check current spend against limits before queuing expensive tasks:

// Check budget before running a large task
const res: Response = await fetch('http://localhost:3000/api/settings/budgets');
const budget: {
dailyLimit: number; monthlyLimit: number;
dailySpend: number; monthlySpend: number;
alertThreshold: number;
} = await res.json();

console.log(`Daily: $${budget.dailySpend.toFixed(2)} / $${budget.dailyLimit.toFixed(2)}`);
console.log(`Monthly: $${budget.monthlySpend.toFixed(2)} / $${budget.monthlyLimit.toFixed(2)}`);

if (budget.dailySpend / budget.dailyLimit > 0.8) {
console.warn('Approaching daily budget limit');
}

Example response:

{
  "dailyLimit": 10.00,
  "monthlyLimit": 200.00,
  "dailySpend": 4.23,
  "monthlySpend": 87.50,
  "alertThreshold": 0.8,
  "updatedAt": "2026-04-03T12:00:00.000Z"
}

Update Budget Policy

POST /api/settings/budgets

Set budget guardrail policy (daily/monthly limits, alert thresholds). Validated with Zod.

Response 200 — Updated budget snapshot

Set conservative budget limits for a team environment:

// Set budget guardrails
await fetch('http://localhost:3000/api/settings/budgets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
  dailyLimit: 10.00,
  monthlyLimit: 200.00,
  alertThreshold: 0.8, // warn at 80% usage
}),
});

Errors: 400 — Zod validation failure

Model Routing

Get Routing Preference

GET /api/settings/routing

Read the current model routing preference.

Response Body

FieldTypeReqDescription
preferenceenum*cost, latency, quality, or manual
// Read current routing preference
const res: Response = await fetch('http://localhost:3000/api/settings/routing');
const { preference }: { preference: string } = await res.json();
console.log(`Current routing: ${preference}`);

Set Routing Preference

POST /api/settings/routing

Update the model routing strategy.

Request Body

FieldTypeReqDescription
preferenceenum*One of: cost, latency, quality, manual

Response 200{ "preference": "<value>" }

Switch between routing strategies depending on your current needs:

// Switch to cost-optimized routing
await fetch('http://localhost:3000/api/settings/routing', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ preference: 'cost' }),
});

Errors: 400 — Invalid preference value

Pricing Registry

Get Pricing Snapshot

GET /api/settings/pricing

Return the current pricing registry snapshot with per-model token costs.

View token pricing for all available models — useful for cost estimation before execution:

// Check model pricing for cost estimation
const res: Response = await fetch('http://localhost:3000/api/settings/pricing');
const pricing: { models: Array<{ modelId: string; inputPer1M: number; outputPer1M: number }> } = await res.json();

pricing.models.forEach((m) => {
console.log(`${m.modelId}: $${m.inputPer1M}/M input, $${m.outputPer1M}/M output`);
});

Example response:

{
  "models": [
    { "modelId": "claude-sonnet-4-6-20250514", "inputPer1M": 3.00, "outputPer1M": 15.00 },
    { "modelId": "claude-haiku-4-20250414", "inputPer1M": 0.80, "outputPer1M": 4.00 }
  ],
  "updatedAt": "2026-04-03T00:00:00.000Z"
}

Refresh Pricing

POST /api/settings/pricing

Force a refresh of the pricing registry from upstream sources.

Response 200 — Refreshed pricing snapshot

// Force a pricing refresh after provider announcements
const res: Response = await fetch('http://localhost:3000/api/settings/pricing', { method: 'POST' });
const pricing: { models: Array<{ modelId: string }> } = await res.json();
console.log(`Pricing updated: ${pricing.models.length} models`);

Chat Settings

Get Chat Model

GET /api/settings/chat

Read the default chat model.

Response Body

FieldTypeReqDescription
defaultModelstring*Model ID used for new conversations
// Read the default chat model
const res: Response = await fetch('http://localhost:3000/api/settings/chat');
const { defaultModel }: { defaultModel: string } = await res.json();
console.log(`Chat model: ${defaultModel}`);

Set Chat Model

PUT /api/settings/chat

Update the default chat model.

Request Body

FieldTypeReqDescription
defaultModelstring*Valid model ID from the supported models list

Change the default model for new chat conversations:

// Switch the default chat model
await fetch('http://localhost:3000/api/settings/chat', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ defaultModel: 'claude-sonnet-4-6-20250514' }),
});

Errors: 400 — Invalid model ID

Runtime Settings

Get Runtime Config

GET /api/settings/runtime

Read SDK timeout and max-turns settings for agent execution.

Response Body

FieldTypeReqDescription
sdkTimeoutSecondsstring*SDK call timeout in seconds (default: 60)
maxTurnsstring*Maximum agent turns per execution (default: 10)
// Read SDK timeout and max-turns settings
const res: Response = await fetch('http://localhost:3000/api/settings/runtime');
const runtime: { sdkTimeoutSeconds: string; maxTurns: string } = await res.json();
console.log(`Timeout: ${runtime.sdkTimeoutSeconds}s, Max turns: ${runtime.maxTurns}`);

Update Runtime Config

POST /api/settings/runtime

Update SDK timeout and/or max-turns settings.

Request Body (all optional)

FieldTypeReqDescription
sdkTimeoutSecondsstringTimeout in seconds (10–300)
maxTurnsstringMax agent turns (1–50)

Increase timeout and turns for complex tasks that need more agent interaction:

// Increase limits for complex agent tasks
await fetch('http://localhost:3000/api/settings/runtime', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sdkTimeoutSeconds: '120', maxTurns: '25' }),
});

Errors: 400 — Value out of range

Ollama Settings

Get Ollama Config

GET /api/settings/ollama

Read Ollama connection settings.

Response Body

FieldTypeReqDescription
baseUrlstring*Ollama server URL (default: http://localhost:11434)
defaultModelstring*Default model name (empty if not set)
// Read Ollama connection settings
const res: Response = await fetch('http://localhost:3000/api/settings/ollama');
const ollama: { baseUrl: string; defaultModel: string } = await res.json();
console.log(`Ollama: ${ollama.baseUrl}, model: ${ollama.defaultModel || '(not set)'}`);

Update Ollama Config

POST /api/settings/ollama

Update Ollama base URL and/or default model.

Request Body (all optional)

FieldTypeReqDescription
baseUrlstringOllama server URL
defaultModelstringDefault model name

Configure a local Ollama instance for offline agent execution:

// Point to a local Ollama instance
await fetch('http://localhost:3000/api/settings/ollama', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ baseUrl: 'http://localhost:11434', defaultModel: 'llama3.1' }),
});

Learning Context

Get Learning Settings

GET /api/settings/learning

Read the learning context character limit.

Response Body

FieldTypeReqDescription
contextCharLimitstring*Max characters for learning context (default: 8000)
// Read the learning context character limit
const res: Response = await fetch('http://localhost:3000/api/settings/learning');
const { contextCharLimit }: { contextCharLimit: string } = await res.json();
console.log(`Learning context limit: ${Number(contextCharLimit).toLocaleString()} chars`);

Update Learning Settings

POST /api/settings/learning

Update the learning context character limit.

Request Body

FieldTypeReqDescription
contextCharLimitstring*Limit in characters (2,000–32,000, step 1,000)

Increase the learning context window so agents retain more project-specific knowledge:

// Double the learning context window
await fetch('http://localhost:3000/api/settings/learning', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contextCharLimit: '16000' }),
});

Errors: 400 — Value out of range or not a multiple of 1,000

Browser Tools

Get Browser Tool Settings

GET /api/settings/browser-tools

Read MCP browser tool enablement and configuration.

Response Body

FieldTypeReqDescription
chromeDevtoolsEnabledboolean*Whether Chrome DevTools MCP is enabled
playwrightEnabledboolean*Whether Playwright MCP is enabled
chromeDevtoolsConfigstring*Chrome DevTools MCP config (JSON string or empty)
playwrightConfigstring*Playwright MCP config (JSON string or empty)
// Read browser tool enablement
const res: Response = await fetch('http://localhost:3000/api/settings/browser-tools');
const tools: { chromeDevtoolsEnabled: boolean; playwrightEnabled: boolean } = await res.json();
console.log(`Chrome DevTools: ${tools.chromeDevtoolsEnabled ? 'on' : 'off'}`);
console.log(`Playwright: ${tools.playwrightEnabled ? 'on' : 'off'}`);

Update Browser Tool Settings

POST /api/settings/browser-tools

Enable/disable browser tools and update their MCP configurations.

Request Body (all optional)

FieldTypeReqDescription
chromeDevtoolsEnabledbooleanToggle Chrome DevTools MCP
playwrightEnabledbooleanToggle Playwright MCP
chromeDevtoolsConfigstringChrome DevTools MCP config JSON
playwrightConfigstringPlaywright MCP config JSON

Enable Chrome DevTools for agents that need browser automation:

// Enable Chrome DevTools MCP for browser-based tasks
await fetch('http://localhost:3000/api/settings/browser-tools', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
  chromeDevtoolsEnabled: true,
  playwrightEnabled: false,
}),
});

Get Web Search Settings

Update Web Search Settings

POST /api/settings/web-search

Enable or disable Exa web search MCP.

Request Body

FieldTypeReqDescription
exaSearchEnabledboolean*Toggle Exa Search MCP
// Enable web search for research-oriented agents
await fetch('http://localhost:3000/api/settings/web-search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exaSearchEnabled: true }),
});

Utility

Get Default Author

GET /api/settings/author-default

Return the OS username as the default task author.

Response Body

FieldTypeReqDescription
authorstring*OS username of the current user
// Get the OS username as default author
const res: Response = await fetch('http://localhost:3000/api/settings/author-default');
const { author }: { author: string } = await res.json();
console.log(`Default author: ${author}`);

Test Runtime Connection

POST /api/settings/test

Test connectivity to a specific agent runtime. Returns connection status and runtime capabilities.

Request Body

FieldTypeReqDescription
runtimestringRuntime ID to test (default: system default runtime)

Response Body

FieldTypeReqDescription
connectedboolean*Whether the runtime responded successfully
runtimestring*Runtime ID that was tested
capabilitiesobject*Runtime capability flags
errorstringError message if connection failed

Test connectivity before running tasks — helpful for diagnosing configuration issues:

// Test runtime connectivity
const res: Response = await fetch('http://localhost:3000/api/settings/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ runtime: 'claude-code' }),
});
const result: { connected: boolean; runtime: string; capabilities: Record<string, any>; error?: string } = await res.json();

if (result.connected) {
console.log(`${result.runtime} is ready`);
console.log('Capabilities:', result.capabilities);
} else {
console.error(`Connection failed: ${result.error}`);
}

Example response:

{
  "connected": true,
  "runtime": "claude-code",
  "capabilities": {
    "streaming": true,
    "toolUse": true,
    "maxTokens": 128000
  }
}

Chat Pins

Pinned entities appear at the top of the chat mention popover so frequently-used tasks, projects, documents, and profiles are always one click away. Pins store a denormalized label and status snapshot so the popover renders without a round-trip — the canonical entity is still resolved by ID when selected.

Get Chat Pins

GET /api/settings/chat/pins

Return the current list of pinned chat mention entries.

Response Body

FieldTypeReqDescription
pinsobject[]*Array of pinned entity entries
pins[].idstring*Entity UUID
pins[].typeenum*Entity type: task, project, workflow, document, schedule, table, or profile
pins[].labelstring*Display label (denormalized from the entity at pin time)
pins[].descriptionstringShort description snapshot
pins[].statusstringEntity status snapshot
pins[].pinnedAtISO 8601*Timestamp when the entity was pinned
// Load pinned entities to pre-populate the mention popover
const res: Response = await fetch('http://localhost:3000/api/settings/chat/pins');
const { pins }: { pins: Array<{ id: string; type: string; label: string; pinnedAt: string }> } = await res.json();

console.log(`${pins.length} pinned entities`);
pins.forEach((p) => console.log(`  [${p.type}] ${p.label}`));

Example response:

{
  "pins": [
    {
      "id": "task-9d4e-a1b2",
      "type": "task",
      "label": "Analyze Q4 revenue trends",
      "status": "completed",
      "pinnedAt": "2026-04-10T09:00:00.000Z"
    },
    {
      "id": "proj-8f3a-4b2c",
      "type": "project",
      "label": "Marketing Analysis",
      "description": "Quarterly marketing data review",
      "pinnedAt": "2026-04-08T14:30:00.000Z"
    }
  ]
}

Update Chat Pins

PUT /api/settings/chat/pins

Replace the full list of pinned chat mention entries. The client is the source of truth — read-modify-write on the client side, then PUT the updated array. Duplicate IDs are de-duplicated server-side (last write wins for pinnedAt).

Request Body

FieldTypeReqDescription
pinsobject[]*Complete replacement list of pinned entries
pins[].idstring*Entity UUID
pins[].typeenum*Entity type: task, project, workflow, document, schedule, table, or profile
pins[].labelstring*Display label
pins[].descriptionstringShort description
pins[].statusstringEntity status
pins[].pinnedAtISO 8601*Pin timestamp

Response 200 — De-duplicated pins array

Add a new task to the pinned list — fetch current pins first, append, then PUT:

// Pin a new task: read → append → write
const existing = await fetch('http://localhost:3000/api/settings/chat/pins')
.then((r: Response) => r.json());

const updated = [
...existing.pins,
{
  id: 'task-c3d4-e5f6',
  type: 'task',
  label: 'Deploy v2.1 release',
  status: 'queued',
  pinnedAt: new Date().toISOString(),
},
];

await fetch('http://localhost:3000/api/settings/chat/pins', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pins: updated }),
});

Errors: 400 — Invalid JSON body or Zod validation failure

Chat Saved Searches

Saved searches let users store named filter combinations for the chat popover and ⌘K palette. Each search is scoped to a surface (task, project, workflow, etc.) and stores the raw filter input string so it can be replayed instantly.

Get Saved Searches

GET /api/settings/chat/saved-searches

Return the current list of saved search filter combinations.

Response Body

FieldTypeReqDescription
searchesobject[]*Array of saved search entries
searches[].idstring*Search entry UUID
searches[].surfaceenum*Search surface: task, project, workflow, document, skill, or profile
searches[].labelstring*Display name for the saved search (max 120 chars)
searches[].filterInputstring*Stored filter string to replay (max 500 chars)
searches[].createdAtISO 8601*Timestamp when the search was saved
// Load saved searches for the ⌘K palette
const res: Response = await fetch('http://localhost:3000/api/settings/chat/saved-searches');
const { searches }: { searches: Array<{ id: string; surface: string; label: string; filterInput: string }> } = await res.json();

console.log(`${searches.length} saved searches`);
searches.forEach((s) => console.log(`  [${s.surface}] ${s.label}: "${s.filterInput}"`));

Example response:

{
  "searches": [
    {
      "id": "srch-1a2b-3c4d",
      "surface": "task",
      "label": "Running tasks",
      "filterInput": "status:running",
      "createdAt": "2026-04-09T11:00:00.000Z"
    },
    {
      "id": "srch-5e6f-7g8h",
      "surface": "document",
      "label": "Design specs",
      "filterInput": "design spec",
      "createdAt": "2026-04-07T16:45:00.000Z"
    }
  ]
}

Update Saved Searches

PUT /api/settings/chat/saved-searches

Replace the full list of saved searches. Duplicate IDs are de-duplicated server-side. Malformed stored values recover to an empty list rather than erroring.

Request Body

FieldTypeReqDescription
searchesobject[]*Complete replacement list of saved searches
searches[].idstring*Search entry UUID
searches[].surfaceenum*Surface: task, project, workflow, document, skill, or profile
searches[].labelstring*Display name (max 120 chars)
searches[].filterInputstring*Filter string (max 500 chars)
searches[].createdAtISO 8601*Creation timestamp

Response 200 — De-duplicated searches array

Save a new filter combination — read first, append the new search, then PUT:

import { v4 as uuidv4 } from 'uuid';

// Save a new search: read → append → write
const existing = await fetch('http://localhost:3000/api/settings/chat/saved-searches')
.then((r: Response) => r.json());

const updated = [
...existing.searches,
{
  id: uuidv4(),
  surface: 'task',
  label: 'P0 tasks this week',
  filterInput: 'priority:0 status:queued',
  createdAt: new Date().toISOString(),
},
];

await fetch('http://localhost:3000/api/settings/chat/saved-searches', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ searches: updated }),
});

Errors: 400 — Invalid JSON body or Zod validation failure

OpenAI OAuth

ainative-business supports both API-key and OAuth authentication for OpenAI. The login/logout routes manage the OAuth browser-based flow for the ChatGPT/Codex App Server integration.

Get OpenAI Login State

GET /api/settings/openai/login

Return the current OpenAI OAuth login state — useful for polling until the browser-based flow completes.

Response Body

FieldTypeReqDescription
statusenum*Login state: idle, pending, success, or error
loginUrlstringOAuth URL to open in a browser (present while pending)
errorstringError message if the flow failed
// Poll the login state while the browser flow is in progress
const res: Response = await fetch('http://localhost:3000/api/settings/openai/login');
const state: { status: string; loginUrl?: string; error?: string } = await res.json();

if (state.status === 'pending' && state.loginUrl) {
console.log(`Open in browser: ${state.loginUrl}`);
} else if (state.status === 'success') {
console.log('OpenAI OAuth login complete');
} else if (state.status === 'error') {
console.error(`Login failed: ${state.error}`);
}

Start OpenAI OAuth Login

POST /api/settings/openai/login

Initiate the OpenAI ChatGPT OAuth browser flow. Sets the auth method to 'oauth' and returns a login URL to open in a browser. Poll GET /api/settings/openai/login to track completion.

Response 200 — Login state object with loginUrl

// Kick off the OAuth flow — open the returned URL in a browser
const res: Response = await fetch('http://localhost:3000/api/settings/openai/login', {
method: 'POST',
});
const state: { status: string; loginUrl?: string } = await res.json();

if (state.loginUrl) {
// Open the URL in the user's default browser
console.log(`Navigate to: ${state.loginUrl}`);
// Then poll GET /api/settings/openai/login until status === "success"
}

Cancel OpenAI OAuth Login

DELETE /api/settings/openai/login

Cancel an in-progress OpenAI OAuth login flow and reset the login state to idle.

Response 200 — Updated login state object with status: "idle"

// Cancel the pending OAuth flow (e.g. user closed the dialog)
const res: Response = await fetch('http://localhost:3000/api/settings/openai/login', {
method: 'DELETE',
});
const state: { status: string } = await res.json();
console.log(`Login state: ${state.status}`); // "idle"

Logout OpenAI (Codex)

POST /api/settings/openai/logout

Log out of the OpenAI Codex App Server session and clear stored Codex credentials.

Response 200{ "success": true }

// Sign out of the Codex App Server session
await fetch('http://localhost:3000/api/settings/openai/logout', { method: 'POST' });
console.log('Codex session cleared');

Environment Settings

Get Environment Settings

GET /api/settings/environment

Read environment-level settings such as the auto-promote skills flag.

Response Body

FieldTypeReqDescription
autoPromoteSkillsboolean*Whether agent-generated skills are automatically promoted to the skills library
// Check whether auto-promote is enabled
const res: Response = await fetch('http://localhost:3000/api/settings/environment');
const { autoPromoteSkills }: { autoPromoteSkills: boolean } = await res.json();
console.log(`Auto-promote skills: ${autoPromoteSkills ? 'on' : 'off'}`);

Example response:

{
  "autoPromoteSkills": false
}

Update Environment Settings

POST /api/settings/environment

Update environment-level settings. Only provided fields are updated.

Request Body (all optional)

FieldTypeReqDescription
autoPromoteSkillsbooleanToggle automatic skill promotion

Response 200 — Updated environment settings object

Enable automatic skill promotion so agents can contribute reusable skills without manual approval:

// Enable auto-promote so agent-generated skills are added to the library automatically
await fetch('http://localhost:3000/api/settings/environment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ autoPromoteSkills: true }),
});

Routing Preferences

ValueDescription
costRoute to the cheapest available model that meets the task requirements.
latencyRoute to the fastest responding model.
qualityRoute to the highest capability model.
manualUser explicitly selects the model per task.