Integration Examples
General Augment is the agent backend for your app. These examples are for product and app engineering teams proving a first integration without exposing project keys to browser or mobile code.
The snippets use raw HTTP and fetch so they can be adapted to any backend. They do not
assume a published SDK. Keep GENAUG_API_KEY in server-side secret storage, pass a
stable opaque app user id on every turn, and store the returned response and trace IDs
for support.
Environment
Section titled “Environment”Use separate project-scoped keys for production, staging, development, and CI:
export GENAUG_API_BASE_URL="https://api.generalaugment.com"export GENAUG_API_KEY="gaadmlive_project_key"export GENAUG_PROJECT_ID="00000000-0000-0000-0000-000000000000"During local contract tests, point the same app code at the local mock:
uv run --project packages/cli genaug mock --host 127.0.0.1 --port 8787 --quietexport GENAUG_API_BASE_URL="http://127.0.0.1:8787"export GENAUG_API_KEY="local-test"Integration Rules
Section titled “Integration Rules”- Browser and mobile clients call your backend, never General Augment directly.
- Your backend attaches the signed-in app user id as
user. - Use
simple,balanced, orcomplexformodelso project operators can change provider models without app code changes. - Send
metadata.featureandmetadata.sourceso usage, trace, and support evidence is easy to filter. - Use
X-Idempotency-Keyfor retryable turns. - Handle
429rate limits and402budget or usage gates without tight retry loops. - Keep app-owned OAuth credentials and risky side effects in your backend until delegated tools, identity links, allowlists, and approval UX are ready.
Server-Side Responses Call
Section titled “Server-Side Responses Call”Use this helper from trusted backend code. It extracts the first assistant
output_text, keeps the response id and trace id, and preserves machine-readable error
fields for app handling.
type GeneralAugmentTurnInput = { userId: string; message: string; feature: string; conversationId?: string;};
export class GeneralAugmentError extends Error { status: number; reason?: string; retryAfter?: string | null;
constructor(message: string, options: { status: number; reason?: string; retryAfter?: string | null }) { super(message); this.name = "GeneralAugmentError"; this.status = options.status; this.reason = options.reason; this.retryAfter = options.retryAfter; }}
export async function runGeneralAugmentTurn(input: GeneralAugmentTurnInput) { const baseUrl = process.env.GENAUG_API_BASE_URL ?? "https://api.generalaugment.com"; const apiKey = process.env.GENAUG_API_KEY;
if (!apiKey) { throw new Error("GENAUG_API_KEY must be configured on the server."); }
const idempotencyKey = [ "ga-turn", input.feature, input.userId, input.conversationId ?? "single", crypto.randomUUID(), ].join(":");
const response = await fetch(`${baseUrl}/v1/responses`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", "X-Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ model: "balanced", user: input.userId, conversation: input.conversationId, input: input.message, stream: false, metadata: { feature: input.feature, source: "app-backend", }, }), });
const payload = await response.json().catch(() => ({}));
if (!response.ok) { const reason = payload.reason ?? payload.detail?.reason; if (response.status === 429) { throw new GeneralAugmentError("General Augment rate limit reached.", { status: 429, reason, retryAfter: response.headers.get("Retry-After"), }); } if (response.status === 402) { throw new GeneralAugmentError("General Augment budget or usage limit reached.", { status: 402, reason, }); } throw new GeneralAugmentError(payload.message ?? payload.detail ?? "General Augment request failed.", { status: response.status, reason, }); }
return { text: extractOutputText(payload), responseId: payload.id, requestId: payload.metadata?.general_augment_request_id ?? payload.metadata?.request_id, traceId: payload.metadata?.general_augment_trace_id ?? payload.metadata?.trace_id, model: payload.metadata?.general_augment_model ?? payload.model, usage: payload.usage, costUsd: payload.metadata?.general_augment_cost_usd, raw: payload, };}
function extractOutputText(payload: any): string { for (const item of payload.output ?? []) { for (const part of item.content ?? []) { if (part.type === "output_text") { return part.text ?? ""; } } } return "";}Store responseId, requestId, traceId, usage, model, costUsd, the app user id,
and the idempotency key in your app logs. Those fields are the support bridge between a
user-visible issue and General Augment traces, usage, and audit surfaces.
Next.js Route Handler Proxy
Section titled “Next.js Route Handler Proxy”In Next.js App Router, expose your own route such as POST /api/assistant. The client
sends only the message and optional conversation id. The route reads the signed-in user
from your server session and calls General Augment with the server-side project key.
import { NextRequest, NextResponse } from "next/server";
type SignedInUser = { id: string;};
async function getSignedInUser(request: NextRequest): Promise<SignedInUser | null> { // Replace this with your app auth. Do not trust a user id from the browser body. const userId = request.headers.get("x-demo-user-id"); return userId ? { id: userId } : null;}
export async function POST(request: NextRequest) { const user = await getSignedInUser(request); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); }
const { message, conversationId } = await request.json(); if (typeof message !== "string" || message.trim().length === 0) { return NextResponse.json({ error: "Message is required." }, { status: 400 }); }
const baseUrl = process.env.GENAUG_API_BASE_URL ?? "https://api.generalaugment.com"; const apiKey = process.env.GENAUG_API_KEY; if (!apiKey) { return NextResponse.json({ error: "Server is missing General Augment configuration." }, { status: 500 }); }
const upstream = await fetch(`${baseUrl}/v1/responses`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", "X-Idempotency-Key": `assistant:${user.id}:${conversationId ?? "new"}:${crypto.randomUUID()}`, }, body: JSON.stringify({ model: "balanced", user: user.id, conversation: conversationId, input: message, stream: false, metadata: { feature: "in-app-assistant", source: "nextjs-route", }, }), });
const payload = await upstream.json().catch(() => ({}));
if (!upstream.ok) { return NextResponse.json( { error: payload.message ?? payload.detail ?? "Assistant request failed.", reason: payload.reason ?? payload.detail?.reason, retryAfter: upstream.headers.get("Retry-After"), }, { status: upstream.status }, ); }
return NextResponse.json({ text: extractOutputText(payload), responseId: payload.id, traceId: payload.metadata?.general_augment_trace_id ?? payload.metadata?.trace_id, usage: payload.usage, });}
function extractOutputText(payload: any): string { return ( payload.output ?.flatMap((item: any) => item.content ?? []) ?.find((part: any) => part.type === "output_text") ?.text ?? "" );}React Client Calling Your Backend
Section titled “React Client Calling Your Backend”The client uses your app route only. It does not import a General Augment key, base URL, or admin endpoint.
import { FormEvent, useState } from "react";
type AssistantMessage = { role: "user" | "assistant"; text: string;};
export function AssistantPanel() { const [messages, setMessages] = useState<AssistantMessage[]>([]); const [input, setInput] = useState(""); const [error, setError] = useState<string | null>(null); const [isLoading, setIsLoading] = useState(false);
async function submit(event: FormEvent) { event.preventDefault(); const message = input.trim(); if (!message) return;
setMessages((current) => [...current, { role: "user", text: message }]); setInput(""); setError(null); setIsLoading(true);
const response = await fetch("/api/assistant", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, conversationId: "support-thread-42" }), });
const payload = await response.json().catch(() => ({})); setIsLoading(false);
if (!response.ok) { if (response.status === 429) { setError(`The assistant is busy. Try again after ${payload.retryAfter ?? "the reset window"}.`); return; } if (response.status === 402) { setError("This assistant has reached its usage limit for now."); return; } setError(payload.error ?? "The assistant could not answer."); return; }
setMessages((current) => [...current, { role: "assistant", text: payload.text ?? "" }]); }
return ( <form onSubmit={submit}> <div aria-live="polite"> {messages.map((message, index) => ( <p key={index}> <strong>{message.role === "user" ? "You" : "Assistant"}:</strong> {message.text} </p> ))} </div> {error ? <p role="alert">{error}</p> : null} <textarea value={input} onChange={(event) => setInput(event.target.value)} /> <button type="submit" disabled={isLoading}> {isLoading ? "Sending..." : "Send"} </button> </form> );}Usage-Threshold Webhook Receiver Sketch
Section titled “Usage-Threshold Webhook Receiver Sketch”General Augment can post usage-threshold events when a project has
billing_webhook_url configured and crosses 80% or 100% of its daily agent-turn limit.
This is an advisory usage signal, not a generic response-completed webhook and not an
approval callback webhook. Approval lifecycle callbacks use the separate
approval_webhook_url project setting and signed approval event contract.
import { NextRequest, NextResponse } from "next/server";
type UsageThresholdEvent = { event: "usage.threshold"; project_id: string; date: string; plan: string; pricing_tier: string; threshold_percent: 80 | 100; agent_turns_count: number; agent_turns_per_day: number; messages_count: number;};
export async function POST(request: NextRequest) { const payload = (await request.json()) as Partial<UsageThresholdEvent>;
if (payload.event !== "usage.threshold") { return NextResponse.json({ error: "Unsupported event." }, { status: 400 }); }
if (payload.project_id !== process.env.GENAUG_PROJECT_ID) { return NextResponse.json({ error: "Unknown project." }, { status: 403 }); }
if (payload.threshold_percent !== 80 && payload.threshold_percent !== 100) { return NextResponse.json({ error: "Unsupported threshold." }, { status: 400 }); }
await enqueueUsageAlert({ projectId: payload.project_id, date: payload.date, thresholdPercent: payload.threshold_percent, agentTurnsCount: payload.agent_turns_count, agentTurnsPerDay: payload.agent_turns_per_day, });
return NextResponse.json({ ok: true });}
async function enqueueUsageAlert(event: { projectId?: string; date?: string; thresholdPercent?: number; agentTurnsCount?: number; agentTurnsPerDay?: number;}) { // Send an internal Slack alert, create a customer-success task, or mark the account // for review. Reconcile with GET /api/v1/admin/projects/{project_id}/usage before // changing product access. console.info("general_augment_usage_threshold", event);}Tool And Action Definition Pattern
Section titled “Tool And Action Definition Pattern”Use one of two patterns:
- App-owned execution: General Augment returns a summary, draft, or structured action proposal. Your app shows confirmation and performs the side effect with app-held credentials.
- Delegated tool execution: expose a hosted MCP server or OpenAPI operation. General Augment registers the operation as a project-defined tool, applies the allowlist and risk policy, and executes through the configured credential boundary.
For a first integration, define app actions as narrow backend endpoints with explicit input schemas and stable operation IDs. Read endpoints can usually auto-execute after review. Write endpoints should require approval. Destructive endpoints should stay disabled until explicitly accepted.
openapi: 3.1.0info: title: Acme Assistant Actions version: 1.0.0paths: /agent-actions/account-summary: post: operationId: create_account_summary summary: Create a read-only account summary for the signed-in user. requestBody: required: true content: application/json: schema: type: object required: [account_id] properties: account_id: type: string responses: "200": description: Account summary. /agent-actions/support-reply: post: operationId: draft_support_reply summary: Draft a support reply for human review. requestBody: required: true content: application/json: schema: type: object required: [ticket_id, tone] properties: ticket_id: type: string tone: type: string enum: [concise, warm, technical] responses: "200": description: Draft reply.Register and review the generated tools:
genaug integrate https://api.example.com/openapi.json \ --name acme-agent \ --description "Acme app assistant"
genaug tools list --project acme-agentgenaug tools toggle draft_support_reply --project acme-agent --enablegenaug deploy ./acme-agent/genaug-agent.yamlIf you review generated OpenAPI tool files under tools/, keep the risk metadata
readable:
id: draft_support_replyenabled: truerisk_level: mediumrequires_approval: truedescription: Draft a support reply for human review before any send action.General Augment does not expose arbitrary inline /v1/responses function callbacks as
the stable public tool interface today. Use OpenAPI-generated tools or MCP servers for
delegated execution.
Human Approval Pattern
Section titled “Human Approval Pattern”For V1, many apps should keep sensitive actions app-owned: ask General Augment for an action proposal, show your own confirmation screen, then execute with your own OAuth credentials.
When you delegate writes to General Augment, approval-required tools create
project-scoped approval rows before side effects run. Built-in approval-required tools
include email_send, calendar_create, and calendar_update. Generated POST,
PUT, and PATCH OpenAPI tools require approval by default, and generated DELETE
tools are disabled by default.
Your backend can show and resolve pending approvals with admin endpoints:
export async function listPendingApprovals() { const baseUrl = process.env.GENAUG_API_BASE_URL ?? "https://api.generalaugment.com"; const projectId = process.env.GENAUG_PROJECT_ID; const apiKey = process.env.GENAUG_API_KEY;
const response = await fetch(`${baseUrl}/api/v1/admin/projects/${projectId}/approvals?status=pending`, { headers: { "X-Admin-Key": apiKey ?? "" }, });
if (!response.ok) { throw new Error(`Could not list approvals: ${response.status}`); }
return response.json();}
export async function resolveApproval(approvalId: string, decision: "approve" | "deny") { const baseUrl = process.env.GENAUG_API_BASE_URL ?? "https://api.generalaugment.com"; const projectId = process.env.GENAUG_PROJECT_ID; const apiKey = process.env.GENAUG_API_KEY;
const response = await fetch( `${baseUrl}/api/v1/admin/projects/${projectId}/approvals/${approvalId}/${decision}`, { method: "POST", headers: { "X-Admin-Key": apiKey ?? "" }, }, );
if (!response.ok) { throw new Error(`Could not ${decision} approval: ${response.status}`); }
return response.json();}Current approval limitations to design around:
- Approval rows expire after the runtime approval TTL, currently about five minutes.
- When
approval_webhook_urlis configured, General Augment sends signedapproval.pending,approval.approved,approval.denied, andapproval.expiredevents with a stableevent_idfor receiver idempotency. - WhatsApp and Telegram can send interactive approval buttons; SMS uses
YESorNO. - App UIs should still poll or refresh pending approvals as the reconciliation fallback.
- Approval is not a credential substitute. The project/user/provider credential and identity-link requirements must still be satisfied before a delegated tool can run.
Local Smoke Checklist
Section titled “Local Smoke Checklist”Run this before product teams consider the integration ready for shared testing:
uv run --project packages/cli genaug mock --host 127.0.0.1 --port 8787 --quietcurl -sS http://127.0.0.1:8787/health/readyThen point the app backend at the mock:
export GENAUG_API_BASE_URL="http://127.0.0.1:8787"export GENAUG_API_KEY="local-test"Checklist:
- Your browser or mobile client calls only your backend route.
- No General Augment project key is present in client bundles, client logs, analytics, prompts, or metadata.
- The app backend can call
/v1/responsesand render the returnedoutput_text. - App logs store response id, request id, trace id, user id, feature, and idempotency key.
402and429responses produce useful product states and do not trigger tight retry loops.- If memory is in scope, two test users have separate store/search/profile/delete checks.
- If tools are in scope, generated tools are reviewed, allowlisted, and disabled where risky.
- If writes are in scope, the app has either app-owned confirmation UX or a delegated General Augment approval path.
- Hosted launch evidence includes
genaug smoke,genaug verify, andgenaug onboarding verify --project <project> --jsonfor the same project.
First 60 Minutes Integration Plan
Section titled “First 60 Minutes Integration Plan”-
Pick the reversible slice. Choose one product surface, one stable app user id, one feature name for metadata, and one project-scoped key.
-
Add the backend client. Add
GENAUG_API_BASE_URLandGENAUG_API_KEYto server-side secret storage. Implement the/v1/responseshelper withmodel,user,input,metadata, andX-Idempotency-Key. -
Proxy through your app backend. Add a route such as
POST /api/assistant. Read the signed-in user from your app session, call General Augment from the server, returntext,responseId, andtraceId. -
Wire a minimal client. Add a UI or product workflow that calls your backend route. Confirm the browser never sees
GENAUG_API_KEY. -
Test locally. Run the local mock, call the app route, verify
output_text, and confirm app logs include the response id, trace id, feature, and stable user id. Exercise402and429handling in app tests or fixtures. -
Decide the next layer. Pick exactly one follow-up: explicit memory, one read-only generated tool, one approval-gated write, or hosted smoke evidence.
Done means the app backend can produce one traced assistant response for one signed-in user, the key stayed server-side, and the app team has the IDs needed to debug that turn with General Augment support.