Skip to content

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.

Use separate project-scoped keys for production, staging, development, and CI:

Terminal window
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:

Terminal window
uv run --project packages/cli genaug mock --host 127.0.0.1 --port 8787 --quiet
export GENAUG_API_BASE_URL="http://127.0.0.1:8787"
export GENAUG_API_KEY="local-test"
  • 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, or complex for model so project operators can change provider models without app code changes.
  • Send metadata.feature and metadata.source so usage, trace, and support evidence is easy to filter.
  • Use X-Idempotency-Key for retryable turns.
  • Handle 429 rate limits and 402 budget 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.

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.

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.

app/api/assistant/route.ts
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 ?? ""
);
}

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>
);
}

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.

app/api/webhooks/general-augment/usage/route.ts
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);
}

Use one of two patterns:

  1. 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.
  2. 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.0
info:
title: Acme Assistant Actions
version: 1.0.0
paths:
/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:

Terminal window
genaug integrate https://api.example.com/openapi.json \
--name acme-agent \
--description "Acme app assistant"
genaug tools list --project acme-agent
genaug tools toggle draft_support_reply --project acme-agent --enable
genaug deploy ./acme-agent/genaug-agent.yaml

If you review generated OpenAPI tool files under tools/, keep the risk metadata readable:

id: draft_support_reply
enabled: true
risk_level: medium
requires_approval: true
description: 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.

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_url is configured, General Augment sends signed approval.pending, approval.approved, approval.denied, and approval.expired events with a stable event_id for receiver idempotency.
  • WhatsApp and Telegram can send interactive approval buttons; SMS uses YES or NO.
  • 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.

Run this before product teams consider the integration ready for shared testing:

Terminal window
uv run --project packages/cli genaug mock --host 127.0.0.1 --port 8787 --quiet
curl -sS http://127.0.0.1:8787/health/ready

Then point the app backend at the mock:

Terminal window
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/responses and render the returned output_text.
  • App logs store response id, request id, trace id, user id, feature, and idempotency key.
  • 402 and 429 responses 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, and genaug onboarding verify --project <project> --json for the same project.
  1. Pick the reversible slice. Choose one product surface, one stable app user id, one feature name for metadata, and one project-scoped key.

  2. Add the backend client. Add GENAUG_API_BASE_URL and GENAUG_API_KEY to server-side secret storage. Implement the /v1/responses helper with model, user, input, metadata, and X-Idempotency-Key.

  3. 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, return text, responseId, and traceId.

  4. Wire a minimal client. Add a UI or product workflow that calls your backend route. Confirm the browser never sees GENAUG_API_KEY.

  5. 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. Exercise 402 and 429 handling in app tests or fixtures.

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