From 69ab059dc2bc17587ea71dcf978a975addb1678d Mon Sep 17 00:00:00 2001 From: mavneox Date: Sat, 4 Apr 2026 21:31:22 -0500 Subject: [PATCH 1/4] docs(GEN-2755): flatten Agent Core API to mirror FE setup canvas Resyncs every docs surface with the flat AgentCorePatch schema shipped in agent-core PR #6. The previous docs described a multi-section schema (identity/overview/inspiration/accounts/look) that diverged from how the GEN Setup canvas FE actually saves data. This commit brings OpenAPI, llms.txt, llms-full.txt, and the agent-core reference page in sync. Changes: - openapi.yaml: flatten AgentCore + AgentCorePatch schemas. Add MonitoredItem, LinkedAccount, VoiceRef component schemas. Delete dead per-item endpoints (/core/inspiration, /core/accounts, /core/look/reference-images, /core/identity/profile-photo) and their schemas (InspirationItem, AccountItem, BrandOverview). Document the three-upstream fan-out in the PATCH description. - reference/agent-core.mdx: complete rewrite with flat field list, full storage map table, two critical distinctions (linked_accounts vs monitored, description vs personality), updated curl + TypeScript examples, per-target 207 response format. - llms.txt: rewrite Step 1 quick-start with the flat PATCH payload. Replace the per-section endpoint list with field-level docs including linked_accounts vs monitored distinction and the keyword auto-mirror behavior. - llms-full.txt: complete rewrite of the Agent Core API section with storage map table, flat GET/PATCH examples, field-level semantics, and validation guardrails (422 on unknown fields, description 500-char cap, personality 20000-char cap, strict enums on identity_type and monitored[].item_type). Co-Authored-By: Claude Opus 4.6 (1M context) --- public/llms-full.txt | 128 ++++--- public/llms.txt | 67 ++-- public/openapi.yaml | 402 ++++++++-------------- src/content/docs/reference/agent-core.mdx | 247 +++++++------ 4 files changed, 406 insertions(+), 438 deletions(-) diff --git a/public/llms-full.txt b/public/llms-full.txt index fa91ab6..746dcd4 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -448,51 +448,80 @@ Prerequisites: Agent must have connected TikTok account (via AURA). Media must b ## Agent Core API (GEN-2755) -**PREFERRED way to configure an agent.** Single-endpoint read/write for the entire agent setup canvas: identity, overview (brand profile), personality, inspiration sources, voice, look (+ reference images), and the agent's own social accounts. Replaces the legacy per-resource TrendPulse calls when you're setting up a new agent or editing its configuration. +**PREFERRED way to configure an agent.** Flat read/write for the entire agent setup canvas in a single call. Every field name mirrors exactly what the GEN Setup canvas FE saves, so developers and the FE speak the same language. Replaces the legacy sequence of per-resource TrendPulse + Rails calls. -**Backing service:** `agent-core` on EC2 (ports 8800 prod, 8801 staging). Exposed through `api.gen.pro` (CloudFront) and `staging-api.gen.pro` for PAT callers, and through `trendpulse.gen.pro` / `staging-trendpulse.gen.pro` for JWT callers. All routes PAT-authenticated via `X-API-Key`. +**Backing service:** `agent-core` on EC2 (ports 8800 prod, 8801 staging). Exposed through `api.gen.pro` (CloudFront) and `staging-api.gen.pro` for PAT callers. All routes PAT-authenticated via `X-API-Key`. + +### Storage map + +The API is a thin facade. Every PATCH field forwards to one of three upstreams: + +| Field | Upstream | Storage | +|---|---|---| +| `brand_name`, `description`, `identity_type`, `goal`, `target_platforms`, `shortform`, `longform`, `onboarding_status`, `keywords`, `monitored`, `linked_accounts` | `PATCH trendpulse.gen.pro/v1/trendpulse/settings` | `trendpulse_configs` + `trendpulse_linked_accounts` | +| `personality` | `PUT api.gen.pro/v1/projects/{pid}/project_nodes/{nid}` with `{project_node: {data}}` | Rails `project_nodes.data` where `node_type='personality'` | +| `default_user_voice` | `PUT api.gen.pro/v1/agents/{id}` with `default_user_voice_attributes` | Rails `agents.default_user_voice` association | + +### Two critical distinctions + +- **`linked_accounts` vs `monitored`.** `linked_accounts` = the agent's OWN brand socials (what it posts from, stored in `trendpulse_linked_accounts`). `monitored` = inspiration sources to watch (accounts/hashtags/keywords, stored in `trendpulse_configs.monitored`). Not the same thing. +- **`description` vs `personality`.** `description` is a short 2-3 sentence brand summary, hard-capped at 500 chars. `personality` is the full persona text, max 20000 chars. A 3000-char persona dumped into `description` returns 422 by design. ### GET /v1/agents/{agent_id}/core -Returns every section of the agent setup canvas in one call. +Returns every field as a flat object. Response: ```json { - "identity": { "name": "Santiago", "profile_photo_url": "https://cdn.example/a.png" }, - "overview": { - "agent_id": "abc123", - "brand_name": "Santiago", - "description": "Santiago is a San Antonio street food scout...", - "identity_type": "character", - "goal": "growth", - "keywords": ["streetfood", "foodtruck", "tacotok"], - "target_platforms": ["tiktok", "instagram"], - "shortform": true, - "longform": false, - "primary_format": null, - "onboarding_status": "active" - }, - "personality": "Santiago grew up eating at his tia's taco stand...", - "inspiration": [{ "id": 1, "url": "https://tiktok.com/@keilapacheco", "platform": "tiktok" }], - "voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" }, - "look": { - "description": "Latino male early 30s, athletic build", - "reference_images": [{ "id": 11, "url": "https://cdn.example/ref1.png" }] - }, - "accounts": [{ "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok" }] + "agent_id": "abc123", + "brand_name": "Santiago", + "description": "San Antonio street food scout who hunts the best tacos.", + "identity_type": "character", + "goal": "growth", + "target_platforms": ["tiktok", "instagram"], + "shortform": true, + "longform": false, + "onboarding_status": "profile_complete", + "keywords": ["streetfood", "tacotok", "sanantonioeats"], + "monitored": [ + { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, + { "handle": "tacotok", "item_type": "hashtag" } + ], + "linked_accounts": [ + { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } + ], + "personality": "Santiago grew up eating at his tia's taco stand in south San Antonio...", + "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" } } ``` ### PATCH /v1/agents/{agent_id}/core -Write any combination of sections in a single call. Merge semantics for `identity`, `overview`, `look.description`. Replace semantics for `personality`, `inspiration`, `voice`, `accounts`. +Flat merge-patch. Send only the fields you want to change. Unknown fields return 422. + +**Field-level semantics:** +- Scalars replace: `brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality` +- List fields use FULL replacement (missing = deleted): `keywords`, `target_platforms`, `monitored`, `linked_accounts` +- `linked_accounts` diffing: rows with `id` are updated in place, rows without `id` are inserted, rows absent from the list are deleted. To add one row without touching others, GET first, append, PATCH. +- `default_user_voice` replaces + +**Keyword auto-mirror:** Sending `keywords` without `monitored` auto-populates `monitored` with `[{handle: kw, item_type: "keyword"}]` so the nightly monitoring cron stays in sync. Send both explicitly if you want different values. -Returns `200 OK` on full success. On partial failure, returns `207 Multi-Status` with per-section results: +**Validation:** +- `description` max 500 chars +- `personality` max 20000 chars +- `identity_type` enum: `"brand"` | `"character"` +- `monitored[].item_type` enum: `"account"` | `"hashtag"` | `"keyword"` +- `linked_accounts[].url` required +- All nested objects reject unknown keys + +Returns `200 OK` on full success. On partial failure, returns `207 Multi-Status` with per-**target** results (not per-field). Targets only appear if the PATCH touched at least one field they own: ```json { - "identity": { "status": "ok", "data": { ... } }, - "overview": { "status": "error", "error": "..." } + "trendpulse": { "status": "ok", "data": { "brand_name": "Santiago", ... } }, + "personality": { "status": "ok", "data": "Santiago grew up..." }, + "voice": { "status": "error", "error": "rails 500 on PUT /v1/agents/abc123" } } ``` @@ -502,34 +531,25 @@ curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ -H "X-API-Key: $GEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ - "identity": { "name": "Santiago" }, - "overview": { - "brand_name": "Santiago", - "description": "Santiago is a San Antonio street food scout...", - "identity_type": "character", - "goal": "growth", - "keywords": ["streetfood", "tacotok"], - "target_platforms": ["tiktok", "instagram"], - "shortform": true - }, - "personality": "Santiago grew up eating at his tia\u0027s taco stand...", - "inspiration": [{ "url": "https://tiktok.com/@keilapacheco", "platform": "tiktok" }], - "look": { "description": "Latino male early 30s, athletic build" }, - "voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" }, - "accounts": [{ "url": "https://tiktok.com/@santiago_real", "platform": "tiktok" }] + "brand_name": "Santiago", + "description": "San Antonio street food scout who hunts the best tacos.", + "identity_type": "character", + "goal": "growth", + "target_platforms": ["tiktok", "instagram"], + "shortform": true, + "longform": false, + "keywords": ["streetfood", "tacotok", "sanantonioeats"], + "monitored": [ + { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" } + ], + "linked_accounts": [ + { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } + ], + "personality": "Santiago grew up eating at his tia\u0027s taco stand in south San Antonio. 28, easy-going, trusts his tongue over reviews, and will fight anyone who says Austin has better tacos.", + "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } }' ``` -### Item-level endpoints (append single items without replacing the list) - -- POST /v1/agents/{agent_id}/core/identity/profile-photo — multipart `file=@photo.jpg` -- POST /v1/agents/{agent_id}/core/inspiration — body `{url, platform?}` -- DELETE /v1/agents/{agent_id}/core/inspiration/{item_id} -- POST /v1/agents/{agent_id}/core/accounts — body `{url, platform?, display_name?}` -- DELETE /v1/agents/{agent_id}/core/accounts/{account_id} -- POST /v1/agents/{agent_id}/core/look/reference-images — body `{url}` -- DELETE /v1/agents/{agent_id}/core/look/reference-images/{image_id} - ## Voice API (Agent Core) All under `/v1/agents/{agent_id}/voice/*`. PAT auth. diff --git a/public/llms.txt b/public/llms.txt index 9430df2..b801e27 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -2,7 +2,7 @@ > GEN is an Autonomous Social Media Agent platform. It detects trends, generates video content, publishes across platforms, and improves automatically. -Two surfaces: **Agent Core** (`api.gen.pro/v1/agents/{id}/core` + `/voice`) for setting up an agent's identity, personality, voice, and brand; and **Auto Content Engine** (`api.gen.pro/v1/autocontentengine/*`) for producing videos from those agents. Both authenticate with an X-API-Key PAT. +Two surfaces: **Agent Core** (`api.gen.pro/v1/agents/{id}/core` + `/voice`) for setting up an agent's brand, personality, inspiration sources, and voice; and **Auto Content Engine** (`api.gen.pro/v1/autocontentengine/*`) for producing videos from those agents. Both authenticate with an X-API-Key PAT. For any new agent, **start with Agent Core**. Generating content before the agent is configured gives you generic results; configuring it first means every downstream feature (content ideas, template fills, voice) speaks in the agent's voice from the first call. @@ -33,29 +33,33 @@ The GEN workflow has two halves — configure the agent, then let it generate co ### Step 1: Configure the Agent Core (most important step for any new agent) -One PATCH writes identity, overview (brand profile), personality, inspiration, voice, look, and accounts. This is what every downstream feature (content ideas, template fills, voice generation) reads from. A blank agent produces generic ideas. A richly configured agent produces specific, on-brand ones. +One flat PATCH writes every field the GEN Setup canvas can save: brand, inspiration sources, personality, voice. Every field name maps 1:1 to how the FE saves it. This is what every downstream feature (content ideas, template fills, voice generation) reads from. A blank agent produces generic ideas. A richly configured agent produces specific, on-brand ones. PATCH /v1/agents/{agent_id}/core ```json { - "identity": { "name": "Santiago" }, - "overview": { - "brand_name": "Santiago", - "description": "Santiago is a San Antonio street food scout...", - "identity_type": "character", - "goal": "growth", - "keywords": ["streetfood", "foodtruck", "tacotok"], - "target_platforms": ["tiktok", "instagram"], - "shortform": true - }, - "personality": "Santiago grew up eating at his tias taco stand...", - "inspiration": [{ "url": "https://tiktok.com/@keithlee", "platform": "tiktok" }], - "look": { "description": "Latino male early 30s, athletic build" }, - "voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" }, - "accounts": [{ "url": "https://tiktok.com/@santiago_sa", "platform": "tiktok" }] + "brand_name": "Santiago", + "description": "San Antonio street food scout who hunts the best tacos.", + "identity_type": "character", + "goal": "growth", + "target_platforms": ["tiktok", "instagram"], + "shortform": true, + "longform": false, + "keywords": ["streetfood", "foodtruck", "tacotok"], + "monitored": [ + { "handle": "https://tiktok.com/@keithlee", "item_type": "account" }, + { "handle": "sanantonioeats", "item_type": "hashtag" } + ], + "linked_accounts": [ + { "url": "https://tiktok.com/@santiago_sa", "platform": "tiktok" } + ], + "personality": "Santiago grew up eating at his tia's taco stand in south San Antonio. 28, easy-going, trusts his tongue over reviews.", + "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } } ``` -Returns 200 on full success or 207 Multi-Status with per-section results. +Returns 200 on full success or 207 Multi-Status with per-target results. Internally this fans out to TrendPulse (brand fields), Rails project_nodes (personality), and Rails agents (voice). You see one call. + +**Critical distinction:** `linked_accounts` = the agent's OWN brand socials (what it posts from). `monitored` = inspiration sources (creators/hashtags/keywords to watch for trending content). Don't mix them. Also: `description` is a short 2-3 sentence summary (max 500 chars); full persona text goes in `personality` (max 20000 chars). Stuffing persona into description returns 422. ### Step 2: Generate content ideas @@ -302,19 +306,20 @@ Column roles: ingredient (user-creatable), video, final_video, stats (system-man - POST /v1/user_jobs?agent_id={id} — publish content (body: {user_job_type: "publish_content", data: JSON string}) ### Agent Core (GEN-2755, recommended for agent setup) -One-call read/write for the entire agent setup canvas (identity, overview, personality, inspiration, voice, look, accounts). Prefer this over per-resource TrendPulse calls when configuring an agent. - -- GET /v1/agents/{agent_id}/core — read all sections in one call -- PATCH /v1/agents/{agent_id}/core — write any combination of sections; 200 on full success, 207 Multi-Status on partial failure -- POST /v1/agents/{agent_id}/core/identity/profile-photo — multipart upload -- POST /v1/agents/{agent_id}/core/inspiration — append inspiration URL -- DELETE /v1/agents/{agent_id}/core/inspiration/{id} -- POST /v1/agents/{agent_id}/core/accounts — append the agent's own social URL -- DELETE /v1/agents/{agent_id}/core/accounts/{id} -- POST /v1/agents/{agent_id}/core/look/reference-images — add look reference image URL -- DELETE /v1/agents/{agent_id}/core/look/reference-images/{id} - -Merge semantics: identity, overview, look.description. Replace semantics: personality, inspiration, voice, accounts. +Flat one-call read/write for the entire agent setup canvas. Field names mirror what the FE saves — no sections, no wrappers. Prefer this over per-resource TrendPulse calls. + +- GET /v1/agents/{agent_id}/core — read all fields in one call +- PATCH /v1/agents/{agent_id}/core — write any subset; 200 on full success, 207 Multi-Status on partial failure + +Fields: +- brand_name, description, identity_type ("brand" | "character"), goal, target_platforms, shortform, longform, onboarding_status +- keywords (flat list of strings) +- monitored (list of {handle, item_type: "account"|"hashtag"|"keyword"}) — inspiration sources +- linked_accounts (list of {id?, url, platform, display_name}) — the agent's OWN brand socials +- personality (string, max 20000 chars) — writes to Rails project_nodes +- default_user_voice ({voice_id, source}) — writes to Rails agents + +Full-list replacement: `keywords`, `monitored`, `target_platforms`, `linked_accounts`. To add one row without touching others, GET first, append, PATCH. Sending `keywords` without `monitored` auto-mirrors keywords into monitored with item_type='keyword'. `description` is hard-capped at 500 chars. Unknown fields return 422. ### Voice (Agent Core) - GET /v1/agents/{agent_id}/voice/library?source={public|user_designed|user_trained|user_elevenlabs} — merged voice list diff --git a/public/openapi.yaml b/public/openapi.yaml index 92ae5fe..4227c7f 100644 --- a/public/openapi.yaml +++ b/public/openapi.yaml @@ -83,7 +83,7 @@ tags: description: (Legacy) Manage the agent's identity, voice, and brand profile. Prefer Agent Core for new work. - name: Agent Core description: | - **Preferred** single-endpoint read/write for the agent setup canvas. One PATCH configures identity, overview, personality, inspiration, voice, look, and accounts. Backed by the agent-core service; exposed through api.gen.pro. + **Preferred** single-endpoint read/write for the agent setup canvas. One flat PATCH updates every brand field, personality, and default voice in a single call. Field names mirror what the FE Setup canvas saves, so developers and the FE speak the same language. Backed by the agent-core service; exposed through api.gen.pro. - name: Agent Voice description: | Voice library, ElevenLabs integration, prompt-based design (4-step flow), audio-sample cloning, async TTS preview. @@ -2903,8 +2903,11 @@ paths: operationId: getAgentCore summary: Get the full agent setup description: | - Returns every section of the agent setup canvas in a single response: - identity, overview, personality, inspiration, voice, look, accounts. + Returns the agent's full setup as a flat object whose field names mirror + exactly what the GEN Setup canvas FE reads and writes. Fans out to + TrendPulse `/trendpulse/settings` (brand fields), Rails `agents` (voice + association), and Rails `project_nodes` (personality) and merges them + into one response. tags: [Agent Core] parameters: - name: agent_id @@ -2922,12 +2925,35 @@ paths: '404': { $ref: '#/components/responses/NotFound' } patch: operationId: patchAgentCore - summary: Update any combination of setup sections + summary: Update any combination of setup fields description: | - Merge semantics for `identity`, `overview`, `look.description`. Replace - semantics for `personality`, `inspiration`, `voice`, `accounts`. Returns - 200 on full success, 207 Multi-Status with per-section results on - partial failure. + Flat merge-patch. Send only the fields you want to change — missing + fields are not forwarded. Field names map 1:1 to the upstream service's + storage columns, with no wrappers or renames. + + **Storage map:** + - `brand_name`, `description`, `identity_type`, `goal`, `keywords`, + `monitored`, `target_platforms`, `shortform`, `longform`, + `onboarding_status`, `linked_accounts` → forwarded in one PATCH to + the TrendPulse settings endpoint, which owns diffing for + `linked_accounts` (delete-then-insert on the full list). + - `personality` → Rails `project_nodes` row with `node_type='personality'`. + - `default_user_voice` → Rails `agents.default_user_voice_attributes`. + + **Guardrails:** + - Unknown fields return 422 — typos fail fast. + - `description` is capped at 500 chars. Full persona text belongs in + `personality` (max 20000 chars). + - `identity_type` is a strict enum: `"brand"` or `"character"`. + - `monitored[].item_type` is a strict enum: `"account"`, `"hashtag"`, + or `"keyword"`. + + **Semantics:** `linked_accounts` and `monitored` use full-list + replacement. To add one row without touching others, read the current + list via GET first, append, then PATCH. + + Returns 200 on full success, 207 Multi-Status with per-target results + (`trendpulse`, `personality`, `voice`) on partial failure. tags: [Agent Core] parameters: - name: agent_id @@ -2942,156 +2968,23 @@ paths: $ref: '#/components/schemas/AgentCorePatch' responses: '200': - description: All sections updated successfully + description: All fields updated successfully content: application/json: schema: $ref: '#/components/schemas/AgentCorePatchResult' '207': - description: Partial success — inspect per-section status + description: Partial success — inspect per-target status content: application/json: schema: $ref: '#/components/schemas/AgentCorePatchResult' '401': { $ref: '#/components/responses/Unauthorized' } - - /agents/{agent_id}/core/identity/profile-photo: - post: - operationId: uploadAgentCoreProfilePhoto - summary: Upload agent profile photo - description: Multipart upload. Proxied to the Rails agents endpoint; returns the CDN URL. - tags: [Agent Core] - parameters: - - name: agent_id - in: path - required: true - schema: { type: string } - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - required: [file] - responses: - '200': - description: Uploaded - content: - application/json: - schema: - type: object - properties: - profile_photo_url: { type: string } - - /agents/{agent_id}/core/inspiration: - post: - operationId: addAgentInspiration - summary: Add one inspiration source - tags: [Agent Core] - parameters: - - name: agent_id - in: path - required: true - schema: { type: string } - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - url: { type: string } - platform: { type: string, enum: [tiktok, instagram, youtube, x, linkedin, pinterest, facebook, threads, website] } - required: [url] - responses: - '201': - description: Created - content: - application/json: - schema: { $ref: '#/components/schemas/InspirationItem' } - - /agents/{agent_id}/core/inspiration/{item_id}: - delete: - operationId: removeAgentInspiration - summary: Remove one inspiration source - tags: [Agent Core] - parameters: - - { name: agent_id, in: path, required: true, schema: { type: string } } - - { name: item_id, in: path, required: true, schema: { type: integer } } - responses: - '204': { description: Deleted } - '404': { $ref: '#/components/responses/NotFound' } - - /agents/{agent_id}/core/accounts: - post: - operationId: addAgentAccount - summary: Add one of the agent's own social accounts - tags: [Agent Core] - parameters: - - { name: agent_id, in: path, required: true, schema: { type: string } } - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - url: { type: string } - platform: { type: string } - display_name: { type: string } - required: [url] - responses: - '201': - description: Created + '422': + description: Validation error — unknown field, bad enum, or length cap exceeded content: application/json: - schema: { $ref: '#/components/schemas/AccountItem' } - - /agents/{agent_id}/core/accounts/{account_id}: - delete: - operationId: removeAgentAccount - summary: Remove one linked account - tags: [Agent Core] - parameters: - - { name: agent_id, in: path, required: true, schema: { type: string } } - - { name: account_id, in: path, required: true, schema: { type: integer } } - responses: - '204': { description: Deleted } - - /agents/{agent_id}/core/look/reference-images: - post: - operationId: addLookReferenceImage - summary: Add a look reference image URL - tags: [Agent Core] - parameters: - - { name: agent_id, in: path, required: true, schema: { type: string } } - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - url: { type: string } - required: [url] - responses: - '201': - description: Created - - /agents/{agent_id}/core/look/reference-images/{image_id}: - delete: - operationId: removeLookReferenceImage - summary: Remove a look reference image - tags: [Agent Core] - parameters: - - { name: agent_id, in: path, required: true, schema: { type: string } } - - { name: image_id, in: path, required: true, schema: { type: integer } } - responses: - '204': { description: Deleted } + schema: { $ref: '#/components/schemas/Error' } # ── Agent Voice (GEN-2755) ──────────────────────────────── /agents/{agent_id}/voice/library: @@ -3992,140 +3885,147 @@ components: description: Machine-readable error code. # ── Agent Core schemas (GEN-2755) ─────────────────────── + # Flat schema — every field name mirrors what the GEN Setup canvas FE + # saves. See api/reference/agent-core for the full storage map. AgentCore: type: object - properties: - identity: - type: object - properties: - name: { type: string, nullable: true } - profile_photo_url: { type: string, nullable: true } - overview: - $ref: '#/components/schemas/BrandOverview' - personality: - type: string - nullable: true - inspiration: - type: array - items: { $ref: '#/components/schemas/InspirationItem' } - voice: - type: object - nullable: true - properties: - voice_id: { type: string } - name: { type: string, nullable: true } - source: { type: string, enum: [public, user_designed, user_trained, user_elevenlabs] } - look: - type: object - properties: - description: { type: string, nullable: true } - reference_images: - type: array - items: - type: object - properties: - id: { type: integer } - url: { type: string } - accounts: - type: array - items: { $ref: '#/components/schemas/AccountItem' } - - BrandOverview: - type: object + description: Full agent setup as returned by GET /agents/{id}/core. properties: agent_id: { type: string } brand_name: { type: string, nullable: true } description: { type: string, nullable: true } identity_type: { type: string, enum: [brand, character], nullable: true } - goal: { type: string, nullable: true } - keywords: - type: array - items: { type: string } + goal: { type: string, nullable: true, description: "Comma-separated, e.g. 'growth,authority,sales'" } target_platforms: type: array items: { type: string } shortform: { type: boolean, nullable: true } longform: { type: boolean, nullable: true } - primary_format: { type: string, nullable: true } onboarding_status: { type: string, nullable: true } + keywords: + type: array + items: { type: string } + description: Flat keyword list used by the monitoring cron. Auto-mirrored into `monitored` on write. + monitored: + type: array + items: { $ref: '#/components/schemas/MonitoredItem' } + description: Inspiration sources — accounts, hashtags, or keywords the agent monitors for trending content. + linked_accounts: + type: array + items: { $ref: '#/components/schemas/LinkedAccount' } + description: The agent's OWN brand social links. NOT the same as `monitored` (which holds sources to watch). + personality: + type: string + nullable: true + description: Full persona text. Canonical source is the Rails `project_nodes` row with `node_type='personality'`. + default_user_voice: + $ref: '#/components/schemas/VoiceRef' AgentCorePatch: type: object - description: "Any combination of sections. Merge semantics for identity/overview/look.description, replace for personality/inspiration/voice/accounts." + additionalProperties: false + description: | + Flat merge-patch body. Send any subset of fields — missing fields are not forwarded. + Unknown fields return 422. `linked_accounts` and `monitored` use full-list replacement + semantics: rows not in the list are deleted. To add without touching others, GET first, + append, then PATCH. properties: - identity: - type: object - properties: - name: { type: string } - profile_photo_url: { type: string } - overview: - type: object - properties: - brand_name: { type: string } - description: { type: string } - identity_type: { type: string, enum: [brand, character] } - goal: { type: string } - keywords: { type: array, items: { type: string } } - target_platforms: { type: array, items: { type: string } } - shortform: { type: boolean } - longform: { type: boolean } - onboarding_status: { type: string } - personality: { type: string } - inspiration: + brand_name: { type: string, maxLength: 200 } + description: + type: string + maxLength: 500 + description: Short 2-3 sentence brand summary. NOT full persona — that goes in `personality`. + identity_type: { type: string, enum: [brand, character] } + goal: { type: string, maxLength: 100 } + target_platforms: type: array - items: - type: object - properties: - url: { type: string } - platform: { type: string } - required: [url] - look: - type: object - properties: - description: { type: string } - voice: - type: object - properties: - voice_id: { type: string } - source: { type: string, enum: [public, user_designed, user_trained, user_elevenlabs] } - accounts: + items: { type: string } + maxItems: 10 + shortform: { type: boolean } + longform: { type: boolean } + onboarding_status: { type: string, maxLength: 50 } + keywords: type: array - items: - type: object - properties: - url: { type: string } - platform: { type: string } - display_name: { type: string } - required: [url] + items: { type: string } + maxItems: 20 + description: Flat keyword list. If set without `monitored`, auto-mirrored into monitored with item_type='keyword'. + monitored: + type: array + items: { $ref: '#/components/schemas/MonitoredItem' } + maxItems: 50 + description: Full inspiration source list (replaces the existing list). + linked_accounts: + type: array + items: { $ref: '#/components/schemas/LinkedAccount' } + maxItems: 20 + personality: + type: string + maxLength: 20000 + description: Full persona text. Written to Rails `project_nodes` where `node_type='personality'`. + default_user_voice: + $ref: '#/components/schemas/VoiceRef' + + MonitoredItem: + type: object + additionalProperties: false + required: [handle, item_type] + properties: + handle: + type: string + minLength: 1 + maxLength: 300 + description: For accounts — a @handle or URL. For hashtags — the tag (with or without #). For keywords — the search term. + item_type: + type: string + enum: [account, hashtag, keyword] + + LinkedAccount: + type: object + additionalProperties: false + required: [url] + properties: + id: + type: integer + description: Row id if updating an existing row. Omit when adding a new account. + url: { type: string, minLength: 1, maxLength: 500 } + platform: + type: string + maxLength: 50 + description: 'tiktok | instagram | youtube | x | linkedin | pinterest | facebook | threads | website | other' + display_name: { type: string, maxLength: 200 } + + VoiceRef: + type: object + additionalProperties: false + required: [voice_id] + properties: + voice_id: { type: string, minLength: 1, maxLength: 200 } + source: + type: string + enum: [public, user_designed, user_trained, user_elevenlabs, eleven_labs] + name: { type: string, maxLength: 200 } AgentCorePatchResult: type: object - description: "Per-section result. status=ok means that section's write succeeded; status=error means it failed (see error field)." + description: | + Per-target result. agent-core fans out to up to three upstreams: + - `trendpulse` — any brand fields (brand_name, description, identity_type, goal, + target_platforms, shortform, longform, onboarding_status, keywords, monitored, + linked_accounts) + - `personality` — Rails project_nodes + - `voice` — Rails agents.default_user_voice_attributes + Each target appears only if the PATCH touched at least one field it owns. + `status=ok` means that target's write succeeded. `status=error` means it failed + (see `error` field). A 207 Multi-Status means at least one target errored. additionalProperties: type: object properties: status: { type: string, enum: [ok, error] } data: - description: "Section-specific payload on success." + description: Target-specific payload on success. error: type: string - description: "Error message on failure." - - InspirationItem: - type: object - properties: - id: { type: integer } - url: { type: string } - platform: { type: string } - - AccountItem: - type: object - properties: - id: { type: integer } - agent_id: { type: string } - url: { type: string } - platform: { type: string } - display_name: { type: string, nullable: true } + description: Error message on failure. VoiceLibraryItem: type: object diff --git a/src/content/docs/reference/agent-core.mdx b/src/content/docs/reference/agent-core.mdx index 1d484b6..bb2e103 100644 --- a/src/content/docs/reference/agent-core.mdx +++ b/src/content/docs/reference/agent-core.mdx @@ -1,64 +1,90 @@ --- title: Agent Core -description: Read and write the entire agent setup canvas — identity, overview, personality, inspiration, voice, look, accounts — in a single call. +description: Read and write an agent's full setup — brand, personality, inspiration sources, voice — in a single flat call. --- import { Aside } from '@astrojs/starlight/components'; -The Agent Core API exposes the agent setup canvas (the page you see at `gen.pro/{agent_id}/setup`) as a flat, PAT-authenticated API. One call reads or writes every section: identity, overview (brand profile), personality, inspiration, voice, look, and the agent's own social accounts. +The Agent Core API exposes the agent setup canvas (`gen.pro/{agent_id}/setup`) as a flat, PAT-authenticated API. One `GET` returns every field; one `PATCH` updates any subset. Field names mirror exactly what the Setup canvas FE saves, so developers and the FE speak the same language. -**Use this when you want to configure an agent programmatically.** It replaces the legacy per-resource sequence of TrendPulse endpoints. +**Use this when you want to configure an agent programmatically.** It replaces the legacy sequence of per-resource TrendPulse + Rails calls. +## Storage map + +The API is a thin facade — every field forwards to an existing upstream service. This is the full map: + +| Field | Upstream endpoint | Storage | +|---|---|---| +| `brand_name` | `PATCH trendpulse.gen.pro/v1/trendpulse/settings` | `trendpulse_configs.brand_name` | +| `description` | same | `trendpulse_configs.description` (max 500 chars) | +| `identity_type` | same | `trendpulse_configs.identity_type` — `brand` or `character` | +| `goal` | same | `trendpulse_configs.goal` (comma-separated) | +| `target_platforms` | same | `trendpulse_configs.target_platforms` (JSON list) | +| `shortform`, `longform` | same | `trendpulse_configs.{shortform,longform}` | +| `onboarding_status` | same | `trendpulse_configs.onboarding_status` | +| `keywords` | same | `trendpulse_configs.keywords` (JSON list of strings) | +| `monitored` | same | `trendpulse_configs.monitored` — inspiration sources | +| `linked_accounts` | same | `trendpulse_linked_accounts` table (delete-then-insert diffing) | +| `personality` | `PUT api.gen.pro/v1/projects/{pid}/project_nodes/{nid}` | Rails `project_nodes.data` where `node_type='personality'` | +| `default_user_voice` | `PUT api.gen.pro/v1/agents/{id}` | Rails `agents.default_user_voice_attributes` | + +A single `PATCH` may fan out to up to three upstreams internally. You see one call and get `200` if all succeed, `207 Multi-Status` with per-target status if any fail. + +## Two important distinctions + +**`linked_accounts` vs `monitored`** — these are different concepts. + +- `linked_accounts` = the agent's **own** brand social accounts (the TikTok/Instagram/website the agent posts from). +- `monitored` = **inspiration sources** — other creators, hashtags, or search keywords the agent watches for trending content. + +Don't mix them. Putting a competitor's TikTok URL in `linked_accounts` will eventually appear on the agent's own "My Socials" list and confuse the content pipeline. + +**`description` vs `personality`** — these are different lengths. + +- `description` is a 2-3 sentence brand summary. Hard-capped at 500 characters. +- `personality` is the full persona text — voice, backstory, behavior rules. Max 20000 characters. + +If you try to put a 3000-character persona into `description`, the API returns a 422. This is intentional — the Lava bug that motivated this API came from callers stuffing persona text into the short-description field. + ## Get the full agent setup ``` GET /v1/agents/{agent_id}/core ``` -Returns every section in one response. +Returns every field as a flat object. Fields that haven't been set return `null` or empty arrays. **Response 200:** + ```json { - "identity": { - "name": "Santiago", - "profile_photo_url": "https://cdn.gen.pro/..." - }, - "overview": { - "agent_id": "abc123", - "brand_name": "Santiago", - "description": "Santiago is a San Antonio-based street food scout...", - "identity_type": "character", - "goal": "growth", - "keywords": ["streetfood", "foodtruck", "tacotok"], - "target_platforms": ["tiktok", "instagram"], - "shortform": true, - "longform": false, - "primary_format": null, - "onboarding_status": "active" - }, - "personality": "Santiago grew up eating at his tia's taco stand...", - "inspiration": [ - { "id": 1, "url": "https://tiktok.com/@keilapacheco", "platform": "tiktok" } + "agent_id": "abc123", + "brand_name": "Santiago", + "description": "Santiago is a San Antonio street food scout who hunts the best tacos in the city.", + "identity_type": "character", + "goal": "growth", + "target_platforms": ["tiktok", "instagram"], + "shortform": true, + "longform": false, + "onboarding_status": "profile_complete", + "keywords": ["streetfood", "tacotok", "sanantonioeats", "foodtruckfinds"], + "monitored": [ + { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, + { "handle": "tacotok", "item_type": "hashtag" } + ], + "linked_accounts": [ + { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago 🌮" } ], - "voice": { + "personality": "Santiago grew up eating at his tía's taco stand in south San Antonio...", + "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" - }, - "look": { - "description": "Latino male early 30s, athletic build...", - "reference_images": [ - { "id": 11, "url": "https://cdn.gen.pro/refs/1.png" } - ] - }, - "accounts": [ - { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok" } - ] + } } ``` @@ -68,100 +94,117 @@ Returns every section in one response. PATCH /v1/agents/{agent_id}/core ``` -Send any combination of sections. Semantics vary per section: +Flat merge-patch body. Send only the fields you want to change — missing fields are not forwarded to any upstream. -| Section | Semantics | +### Field-level semantics + +| Field | Semantics | |---|---| -| `identity` | **Merge** — only provided fields are updated | -| `overview` | **Merge** — only provided fields | -| `personality` | **Replace** — string replaces the full personality | -| `inspiration` | **Replace** — array replaces the full inspiration list | -| `voice` | **Replace** — `{voice_id, source}` sets the default voice | -| `look.description` | **Merge** — only updates the description (use item-level endpoints below for reference images) | -| `accounts` | **Replace** — array replaces the full account list | +| Scalars (`brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality`) | Replace | +| `keywords` | Replace the full list | +| `target_platforms` | Replace the full list | +| `monitored` | **Replace the full list** — items you don't send are deleted | +| `linked_accounts` | **Replace the full list** — rows you don't send are deleted. Rows with `id` are updated in place; rows without `id` are inserted. | +| `default_user_voice` | Replace | + +To add a single `monitored` item or `linked_account` without touching others, `GET /core` first, append, then `PATCH` with the full list. + +### Validation guardrails + +- Unknown fields return **422** (`extra="forbid"` on every model). +- `description` max 500 chars. +- `personality` max 20000 chars. +- `identity_type` strict enum: `"brand"` | `"character"`. +- `monitored[].item_type` strict enum: `"account"` | `"hashtag"` | `"keyword"`. +- `linked_accounts[].url` required. +- `monitored[]` and `linked_accounts[]` also reject unknown keys. + +### Keyword → monitored auto-mirror + +If you send `keywords` without also sending `monitored`, the writer automatically populates `monitored` with `[{handle: kw, item_type: "keyword"}]` for each keyword, so the nightly monitoring cron stays in sync. This matches what the brand-analysis onboarding task does. If you send both, your explicit `monitored` wins — the auto-mirror is skipped. -**Response 200** (all sections succeeded) or **207 Multi-Status** (partial failure) with per-section results: +### Response + +`200` on full success, `207 Multi-Status` on partial failure. The body is a map keyed by **upstream target**, not by request field: ```json { - "identity": { "status": "ok", "data": { "name": "Santiago", "profile_photo_url": null } }, - "overview": { "status": "ok", "data": { ... } }, - "inspiration": { "status": "error", "error": "invalid url" } + "trendpulse": { "status": "ok", "data": { "brand_name": "Santiago", "keywords": ["streetfood"], ... } }, + "personality": { "status": "ok", "data": "Santiago grew up eating at..." }, + "voice": { "status": "error", "error": "rails 500 on PUT /v1/agents/abc123" } } ``` -**Example — set up a character in one call:** +Targets only appear if the PATCH touched at least one field they own. A PATCH that only sets `brand_name` returns `{ "trendpulse": { "status": "ok", ... } }` — no `personality` or `voice` entry. + +## Example — set up a character in one call ```bash curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ -H "X-API-Key: $GEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ - "identity": { "name": "Santiago" }, - "overview": { - "brand_name": "Santiago", - "description": "Santiago is a San Antonio street food scout...", - "identity_type": "character", - "goal": "growth", - "keywords": ["streetfood", "tacotok"], - "target_platforms": ["tiktok", "instagram"], - "shortform": true - }, - "personality": "Santiago grew up eating at his tia'\''s taco stand...", - "inspiration": [ - { "url": "https://tiktok.com/@keilapacheco", "platform": "tiktok" } + "brand_name": "Santiago", + "description": "Santiago is a San Antonio street food scout who hunts the best tacos in the city.", + "identity_type": "character", + "goal": "growth", + "target_platforms": ["tiktok", "instagram"], + "shortform": true, + "longform": false, + "onboarding_status": "profile_complete", + "keywords": ["streetfood", "tacotok", "sanantonioeats"], + "monitored": [ + { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, + { "handle": "tacotok", "item_type": "hashtag" } + ], + "linked_accounts": [ + { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], - "look": { "description": "Latino male early 30s, athletic build" }, - "voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" }, - "accounts": [ - { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok" } - ] + "personality": "Santiago grew up eating at his tía'\''s taco stand in south San Antonio. He'\''s 28, easy-going, trusts his tongue over reviews, and will fight anyone who says Austin has better tacos.", + "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } }' ``` -## Identity — profile photo upload (multipart) +**TypeScript SDK equivalent:** -The photo can't fit in a JSON PATCH, so it has its own endpoint. +```typescript +import { GenClient } from '@poweredbygen/gen-sdk'; -``` -POST /v1/agents/{agent_id}/core/identity/profile-photo -Content-Type: multipart/form-data -``` +const gen = new GenClient({ apiKey: process.env.GEN_API_KEY! }); -**Form fields:** -- `file` — JPEG, PNG, or WebP +const result = await gen.agents.updateCore('abc123', { + brand_name: 'Santiago', + description: 'San Antonio street food scout who hunts the best tacos.', + identity_type: 'character', + goal: 'growth', + target_platforms: ['tiktok', 'instagram'], + shortform: true, + keywords: ['streetfood', 'tacotok', 'sanantonioeats'], + monitored: [ + { handle: 'https://tiktok.com/@keilapacheco', item_type: 'account' }, + ], + linked_accounts: [ + { url: 'https://tiktok.com/@santiago_real', platform: 'tiktok' }, + ], + personality: 'Santiago grew up eating at his tía\'s taco stand...', + default_user_voice: { voice_id: '21m00Tcm4TlvDq8ikWAM', source: 'public' }, +}); -**Response 200:** -```json -{ "profile_photo_url": "https://cdn.gen.pro/agents/abc123/photo.png" } +if (result.personality?.status === 'error') { + console.error('personality write failed:', result.personality.error); +} ``` -## Item-level endpoints - -Add or remove a single inspiration source, social account, or reference image without replacing the whole list. - -### Inspiration - -- `POST /v1/agents/{agent_id}/core/inspiration` — body `{"url": "...", "platform": "tiktok"}` -- `DELETE /v1/agents/{agent_id}/core/inspiration/{id}` - -### Accounts (the agent's own socials) - -- `POST /v1/agents/{agent_id}/core/accounts` — body `{"url": "...", "platform": "tiktok", "display_name": "..."}` (platform auto-detected from URL if omitted) -- `DELETE /v1/agents/{agent_id}/core/accounts/{id}` - -### Look reference images - -- `POST /v1/agents/{agent_id}/core/look/reference-images` — body `{"url": "..."}` -- `DELETE /v1/agents/{agent_id}/core/look/reference-images/{id}` - ## Errors | Code | Meaning | |---|---| -| 400 | Missing required field (e.g. POST inspiration without `url`) | | 401 | Missing or invalid `X-API-Key` | -| 404 | Agent not found, or item-level resource not found | -| 207 | `PATCH /core` — partial success. Inspect per-section `status` in the response body. | +| 404 | Agent not found | +| 422 | Validation error — unknown field, bad enum, length cap exceeded, or missing required sub-field like `monitored[].item_type` | +| 207 | Partial success. Inspect per-target `status` in the response body. | + +## Related -See also the [Voice API reference](/reference/agent-voice/) for voice library, cloning, design, and preview. +- [Voice API](/reference/agent-voice/) — voice library, design, cloning, preview (separate endpoint tree) +- [Discovery API](/reference/discovery/) — listing agents, workspaces, organizations From 27a70c8f69633e988035a9ad7533d87bc28152b5 Mon Sep 17 00:00:00 2001 From: mavneox Date: Mon, 6 Apr 2026 12:00:14 -0500 Subject: [PATCH 2/4] fix: strip staging URLs, internal infra, and DB details from public docs + add research_topics field Security: removed staging-api.gen.pro URLs, EC2/port references, internal storage map (trendpulse_configs, project_nodes), internal routing details, Dynamic XYZ auth provider name, and Remotion Lambda reference from all public-facing surfaces (docs, llms.txt, llms-full.txt, openapi.yaml). Also: added research_topics field to Agent Core docs, improved API key onboarding on overview page, and cleaned up response examples. Co-Authored-By: Claude Opus 4.6 (1M context) --- public/llms-full.txt | 42 ++++++++-------- public/llms.txt | 11 +++-- public/openapi.yaml | 6 +-- src/content/docs/guides/authentication.mdx | 2 +- src/content/docs/guides/quickstart.mdx | 9 +++- src/content/docs/reference/agent-core.mdx | 57 +++++++++++----------- src/content/docs/reference/agent-voice.mdx | 6 +-- src/content/docs/reference/overview.mdx | 23 ++++++--- src/content/docs/reference/rendering.mdx | 2 +- 9 files changed, 87 insertions(+), 71 deletions(-) diff --git a/public/llms-full.txt b/public/llms-full.txt index 746dcd4..a3178b9 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -446,25 +446,19 @@ Poll: GET /v1/user_jobs/{id}?agent_id={id} Prerequisites: Agent must have connected TikTok account (via AURA). Media must be publicly accessible. -## Agent Core API (GEN-2755) +## Agent Core API -**PREFERRED way to configure an agent.** Flat read/write for the entire agent setup canvas in a single call. Every field name mirrors exactly what the GEN Setup canvas FE saves, so developers and the FE speak the same language. Replaces the legacy sequence of per-resource TrendPulse + Rails calls. +**PREFERRED way to configure an agent.** Flat read/write for the entire agent setup canvas in a single call. Every field name mirrors exactly what the GEN Setup canvas saves, so developers and the app speak the same language. -**Backing service:** `agent-core` on EC2 (ports 8800 prod, 8801 staging). Exposed through `api.gen.pro` (CloudFront) and `staging-api.gen.pro` for PAT callers. All routes PAT-authenticated via `X-API-Key`. +**Base URL:** `https://api.gen.pro/v1`. All routes authenticated via `X-API-Key` header with a Personal Access Token. -### Storage map +### Fields -The API is a thin facade. Every PATCH field forwards to one of three upstreams: - -| Field | Upstream | Storage | -|---|---|---| -| `brand_name`, `description`, `identity_type`, `goal`, `target_platforms`, `shortform`, `longform`, `onboarding_status`, `keywords`, `monitored`, `linked_accounts` | `PATCH trendpulse.gen.pro/v1/trendpulse/settings` | `trendpulse_configs` + `trendpulse_linked_accounts` | -| `personality` | `PUT api.gen.pro/v1/projects/{pid}/project_nodes/{nid}` with `{project_node: {data}}` | Rails `project_nodes.data` where `node_type='personality'` | -| `default_user_voice` | `PUT api.gen.pro/v1/agents/{id}` with `default_user_voice_attributes` | Rails `agents.default_user_voice` association | +A single PATCH may touch multiple internal systems. You see one call and get `200` if all succeed, or `207 Multi-Status` with per-section status if any one section fails. ### Two critical distinctions -- **`linked_accounts` vs `monitored`.** `linked_accounts` = the agent's OWN brand socials (what it posts from, stored in `trendpulse_linked_accounts`). `monitored` = inspiration sources to watch (accounts/hashtags/keywords, stored in `trendpulse_configs.monitored`). Not the same thing. +- **`linked_accounts` vs `monitored` vs `research_topics`.** `linked_accounts` = the agent's OWN brand socials (the TikTok/Instagram/website the agent posts from). `monitored` = inspiration sources the agent watches for trending content (other creators, hashtags, or search keywords). `research_topics` = expertise areas the agent stays current on — the platform researches each daily and feeds results into content ideas (labeled "Expertise" in the UI; only include topics where recent events matter). Not the same thing — putting a competitor's URL into `linked_accounts` will eventually show up on the agent's own "My Socials" list. - **`description` vs `personality`.** `description` is a short 2-3 sentence brand summary, hard-capped at 500 chars. `personality` is the full persona text, max 20000 chars. A 3000-char persona dumped into `description` returns 422 by design. ### GET /v1/agents/{agent_id}/core @@ -488,6 +482,11 @@ Response: { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, { "handle": "tacotok", "item_type": "hashtag" } ], + "research_topics": [ + { "topic": "new food truck openings in San Antonio" }, + { "topic": "viral street food TikTok moments" }, + { "topic": "Texas food festival announcements" } + ], "linked_accounts": [ { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], @@ -502,7 +501,7 @@ Flat merge-patch. Send only the fields you want to change. Unknown fields return **Field-level semantics:** - Scalars replace: `brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality` -- List fields use FULL replacement (missing = deleted): `keywords`, `target_platforms`, `monitored`, `linked_accounts` +- List fields use FULL replacement (missing = deleted): `keywords`, `target_platforms`, `monitored`, `research_topics`, `linked_accounts` - `linked_accounts` diffing: rows with `id` are updated in place, rows without `id` are inserted, rows absent from the list are deleted. To add one row without touching others, GET first, append, PATCH. - `default_user_voice` replaces @@ -513,15 +512,16 @@ Flat merge-patch. Send only the fields you want to change. Unknown fields return - `personality` max 20000 chars - `identity_type` enum: `"brand"` | `"character"` - `monitored[].item_type` enum: `"account"` | `"hashtag"` | `"keyword"` +- `research_topics` max 20 items, each `topic` max 500 chars - `linked_accounts[].url` required - All nested objects reject unknown keys -Returns `200 OK` on full success. On partial failure, returns `207 Multi-Status` with per-**target** results (not per-field). Targets only appear if the PATCH touched at least one field they own: +Returns `200 OK` on full success. On partial failure, returns `207 Multi-Status` with per-**section** results (not per-field). Sections only appear if the PATCH touched at least one field they own: ```json { - "trendpulse": { "status": "ok", "data": { "brand_name": "Santiago", ... } }, + "brand": { "status": "ok", "data": { "brand_name": "Santiago", ... } }, "personality": { "status": "ok", "data": "Santiago grew up..." }, - "voice": { "status": "error", "error": "rails 500 on PUT /v1/agents/abc123" } + "voice": { "status": "error", "error": "voice service unavailable" } } ``` @@ -542,6 +542,10 @@ curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ "monitored": [ { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" } ], + "research_topics": [ + { "topic": "new food truck openings in San Antonio" }, + { "topic": "viral street food TikTok moments" } + ], "linked_accounts": [ { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], @@ -717,9 +721,9 @@ Each idea includes: - rationale — why this idea works, with specific data points ### Asset Sources -- Social content (TikTok/IG/YouTube): assets.gen.buzz (permanent S3 URLs) -- Web research assets: gen-agentic-assets S3 bucket -- User uploads: Rails ContentResource +- Social content (TikTok/IG/YouTube): permanent CDN URLs +- Web research assets: sourced by the research agent +- User uploads: via the Content Resources API ### Research-Backed Generation The agent automatically: diff --git a/public/llms.txt b/public/llms.txt index b801e27..d4f7e18 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -57,7 +57,7 @@ PATCH /v1/agents/{agent_id}/core "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } } ``` -Returns 200 on full success or 207 Multi-Status with per-target results. Internally this fans out to TrendPulse (brand fields), Rails project_nodes (personality), and Rails agents (voice). You see one call. +Returns 200 on full success or 207 Multi-Status with per-section results. A single call updates all relevant sections internally. **Critical distinction:** `linked_accounts` = the agent's OWN brand socials (what it posts from). `monitored` = inspiration sources (creators/hashtags/keywords to watch for trending content). Don't mix them. Also: `description` is a short 2-3 sentence summary (max 500 chars); full persona text goes in `personality` (max 20000 chars). Stuffing persona into description returns 422. @@ -315,11 +315,12 @@ Fields: - brand_name, description, identity_type ("brand" | "character"), goal, target_platforms, shortform, longform, onboarding_status - keywords (flat list of strings) - monitored (list of {handle, item_type: "account"|"hashtag"|"keyword"}) — inspiration sources +- research_topics (list of {topic: string}, max 20 items, each max 500 chars) — subjects the agent stays current on (labeled "Expertise" in UI). Platform researches daily and feeds results into content ideas. Leave empty for evergreen niches. - linked_accounts (list of {id?, url, platform, display_name}) — the agent's OWN brand socials -- personality (string, max 20000 chars) — writes to Rails project_nodes -- default_user_voice ({voice_id, source}) — writes to Rails agents +- personality (string, max 20000 chars) — full persona text +- default_user_voice ({voice_id, source}) — default TTS voice -Full-list replacement: `keywords`, `monitored`, `target_platforms`, `linked_accounts`. To add one row without touching others, GET first, append, PATCH. Sending `keywords` without `monitored` auto-mirrors keywords into monitored with item_type='keyword'. `description` is hard-capped at 500 chars. Unknown fields return 422. +Full-list replacement: `keywords`, `monitored`, `research_topics`, `target_platforms`, `linked_accounts`. To add one row without touching others, GET first, append, PATCH. Sending `keywords` without `monitored` auto-mirrors keywords into monitored with item_type='keyword'. `description` is hard-capped at 500 chars. Unknown fields return 422. ### Voice (Agent Core) - GET /v1/agents/{agent_id}/voice/library?source={public|user_designed|user_trained|user_elevenlabs} — merged voice list @@ -340,7 +341,7 @@ Voice delete + preview: - POST /v1/agents/{agent_id}/voice/{voice_id}/preview — enqueue TTS preview job (ASYNC, returns `{user_job_id}`) - GET /v1/agents/{agent_id}/voice/preview/{job_id} — poll preview job status -Auth: PAT (`X-API-Key`). Runs on `agent-core` service on EC2, exposed via `api.gen.pro` CloudFront + `staging-api.gen.pro` staging. +Auth: PAT (`X-API-Key`) via `api.gen.pro`. ## Agentic Chat API diff --git a/public/openapi.yaml b/public/openapi.yaml index 4227c7f..5551df4 100644 --- a/public/openapi.yaml +++ b/public/openapi.yaml @@ -32,11 +32,9 @@ info: servers: - url: https://api.gen.pro/v1 - description: Production - - url: https://stagingapi.gen.pro/v1 - description: Staging + description: Auto Content Engine API - url: https://agent.gen.pro/v1 - description: Production (Agent Chat API) + description: Agent Chat API security: - apiKey: [] diff --git a/src/content/docs/guides/authentication.mdx b/src/content/docs/guides/authentication.mdx index d773e55..c8c8d6a 100644 --- a/src/content/docs/guides/authentication.mdx +++ b/src/content/docs/guides/authentication.mdx @@ -154,7 +154,7 @@ curl https://api.gen.pro/v1/me \ -H "Authorization: Bearer " ``` -JWTs are issued by Dynamic XYZ during user login. They contain the user's wallet address and are validated against the GEN user database. +JWTs are issued during user login through the GEN web app and are validated against the GEN user database. **When to use each method:** diff --git a/src/content/docs/guides/quickstart.mdx b/src/content/docs/guides/quickstart.mdx index add8050..3c54fa9 100644 --- a/src/content/docs/guides/quickstart.mdx +++ b/src/content/docs/guides/quickstart.mdx @@ -63,7 +63,12 @@ curl -s -X PATCH "https://api.gen.pro/v1/agents/$AGENT_ID/core" \ "goal": "growth", "keywords": ["streetfood", "foodtruck", "tacotok"], "target_platforms": ["tiktok", "instagram"], - "shortform": true + "shortform": true, + "research_topics": [ + {"topic": "new food truck openings in San Antonio"}, + {"topic": "viral street food TikTok moments"}, + {"topic": "Texas food festival announcements"} + ] }, "personality": "Santiago grew up eating at his tias taco stand on the East Side. He believes the best food comes from the smallest kitchens. He is warm, specific, never condescending, always hungry.", "inspiration": [ @@ -182,7 +187,7 @@ See the [Content Resources guide](/reference/content-resources/) for downloading - + diff --git a/src/content/docs/reference/agent-core.mdx b/src/content/docs/reference/agent-core.mdx index bb2e103..28edac1 100644 --- a/src/content/docs/reference/agent-core.mdx +++ b/src/content/docs/reference/agent-core.mdx @@ -7,39 +7,23 @@ import { Aside } from '@astrojs/starlight/components'; The Agent Core API exposes the agent setup canvas (`gen.pro/{agent_id}/setup`) as a flat, PAT-authenticated API. One `GET` returns every field; one `PATCH` updates any subset. Field names mirror exactly what the Setup canvas FE saves, so developers and the FE speak the same language. -**Use this when you want to configure an agent programmatically.** It replaces the legacy sequence of per-resource TrendPulse + Rails calls. +**Use this when you want to configure an agent programmatically.** It replaces having to call multiple endpoints separately. -## Storage map +## Fields -The API is a thin facade — every field forwards to an existing upstream service. This is the full map: +A single `PATCH` may update multiple internal systems at once. You see one call and get `200` if all succeed, or `207 Multi-Status` with per-section status if any one section fails. -| Field | Upstream endpoint | Storage | -|---|---|---| -| `brand_name` | `PATCH trendpulse.gen.pro/v1/trendpulse/settings` | `trendpulse_configs.brand_name` | -| `description` | same | `trendpulse_configs.description` (max 500 chars) | -| `identity_type` | same | `trendpulse_configs.identity_type` — `brand` or `character` | -| `goal` | same | `trendpulse_configs.goal` (comma-separated) | -| `target_platforms` | same | `trendpulse_configs.target_platforms` (JSON list) | -| `shortform`, `longform` | same | `trendpulse_configs.{shortform,longform}` | -| `onboarding_status` | same | `trendpulse_configs.onboarding_status` | -| `keywords` | same | `trendpulse_configs.keywords` (JSON list of strings) | -| `monitored` | same | `trendpulse_configs.monitored` — inspiration sources | -| `linked_accounts` | same | `trendpulse_linked_accounts` table (delete-then-insert diffing) | -| `personality` | `PUT api.gen.pro/v1/projects/{pid}/project_nodes/{nid}` | Rails `project_nodes.data` where `node_type='personality'` | -| `default_user_voice` | `PUT api.gen.pro/v1/agents/{id}` | Rails `agents.default_user_voice_attributes` | +## Important distinctions -A single `PATCH` may fan out to up to three upstreams internally. You see one call and get `200` if all succeed, `207 Multi-Status` with per-target status if any fail. - -## Two important distinctions - -**`linked_accounts` vs `monitored`** — these are different concepts. +**`linked_accounts` vs `monitored` vs `research_topics`** — these are three different concepts. - `linked_accounts` = the agent's **own** brand social accounts (the TikTok/Instagram/website the agent posts from). - `monitored` = **inspiration sources** — other creators, hashtags, or search keywords the agent watches for trending content. +- `research_topics` = **expertise areas** (labeled "Expertise" in the UI) — subjects the agent needs to stay current on. The platform automatically researches each topic daily and feeds the results into content generation. Only include topics where recent events matter — leave empty for evergreen niches. Don't mix them. Putting a competitor's TikTok URL in `linked_accounts` will eventually appear on the agent's own "My Socials" list and confuse the content pipeline. @@ -48,7 +32,7 @@ Don't mix them. Putting a competitor's TikTok URL in `linked_accounts` will even - `description` is a 2-3 sentence brand summary. Hard-capped at 500 characters. - `personality` is the full persona text — voice, backstory, behavior rules. Max 20000 characters. -If you try to put a 3000-character persona into `description`, the API returns a 422. This is intentional — the Lava bug that motivated this API came from callers stuffing persona text into the short-description field. +If you try to put a 3000-character persona into `description`, the API returns a 422. This is intentional. ## Get the full agent setup @@ -76,6 +60,11 @@ Returns every field as a flat object. Fields that haven't been set return `null` { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, { "handle": "tacotok", "item_type": "hashtag" } ], + "research_topics": [ + { "topic": "new food truck openings in San Antonio" }, + { "topic": "viral street food TikTok moments" }, + { "topic": "Texas food festival announcements" } + ], "linked_accounts": [ { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago 🌮" } ], @@ -94,7 +83,7 @@ Returns every field as a flat object. Fields that haven't been set return `null` PATCH /v1/agents/{agent_id}/core ``` -Flat merge-patch body. Send only the fields you want to change — missing fields are not forwarded to any upstream. +Flat merge-patch body. Send only the fields you want to change — omitted fields are left unchanged. ### Field-level semantics @@ -104,6 +93,7 @@ Flat merge-patch body. Send only the fields you want to change — missing field | `keywords` | Replace the full list | | `target_platforms` | Replace the full list | | `monitored` | **Replace the full list** — items you don't send are deleted | +| `research_topics` | **Replace the full list** — topics you don't send are deleted. Max 20 items. | | `linked_accounts` | **Replace the full list** — rows you don't send are deleted. Rows with `id` are updated in place; rows without `id` are inserted. | | `default_user_voice` | Replace | @@ -116,8 +106,9 @@ To add a single `monitored` item or `linked_account` without touching others, `G - `personality` max 20000 chars. - `identity_type` strict enum: `"brand"` | `"character"`. - `monitored[].item_type` strict enum: `"account"` | `"hashtag"` | `"keyword"`. +- `research_topics` max 20 items, each `topic` max 500 chars. - `linked_accounts[].url` required. -- `monitored[]` and `linked_accounts[]` also reject unknown keys. +- `monitored[]`, `research_topics[]`, and `linked_accounts[]` also reject unknown keys. ### Keyword → monitored auto-mirror @@ -129,13 +120,13 @@ If you send `keywords` without also sending `monitored`, the writer automaticall ```json { - "trendpulse": { "status": "ok", "data": { "brand_name": "Santiago", "keywords": ["streetfood"], ... } }, + "brand": { "status": "ok", "data": { "brand_name": "Santiago", "keywords": ["streetfood"], ... } }, "personality": { "status": "ok", "data": "Santiago grew up eating at..." }, - "voice": { "status": "error", "error": "rails 500 on PUT /v1/agents/abc123" } + "voice": { "status": "error", "error": "voice service unavailable" } } ``` -Targets only appear if the PATCH touched at least one field they own. A PATCH that only sets `brand_name` returns `{ "trendpulse": { "status": "ok", ... } }` — no `personality` or `voice` entry. +Sections only appear if the PATCH touched at least one field they own. A PATCH that only sets `brand_name` returns `{ "brand": { "status": "ok", ... } }` — no `personality` or `voice` entry. ## Example — set up a character in one call @@ -157,6 +148,10 @@ curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ { "handle": "https://tiktok.com/@keilapacheco", "item_type": "account" }, { "handle": "tacotok", "item_type": "hashtag" } ], + "research_topics": [ + { "topic": "new food truck openings in San Antonio" }, + { "topic": "viral street food TikTok moments" } + ], "linked_accounts": [ { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], @@ -183,6 +178,10 @@ const result = await gen.agents.updateCore('abc123', { monitored: [ { handle: 'https://tiktok.com/@keilapacheco', item_type: 'account' }, ], + research_topics: [ + { topic: 'new food truck openings in San Antonio' }, + { topic: 'viral street food TikTok moments' }, + ], linked_accounts: [ { url: 'https://tiktok.com/@santiago_real', platform: 'tiktok' }, ], diff --git a/src/content/docs/reference/agent-voice.mdx b/src/content/docs/reference/agent-voice.mdx index 0769892..bcc6912 100644 --- a/src/content/docs/reference/agent-voice.mdx +++ b/src/content/docs/reference/agent-voice.mdx @@ -1,14 +1,14 @@ --- title: Voice -description: List, design, clone, preview, and manage voices on an agent. Powered by the Agent Core service. +description: List, design, clone, preview, and manage voices on an agent. --- import { Aside } from '@astrojs/starlight/components'; -The Voice API lives alongside Agent Core under `/v1/agents/{agent_id}/voice/*`. It unifies voice library listing, ElevenLabs integration, prompt-based voice design, audio sample cloning, TTS preview, and voice deletion. +The Voice API lives alongside Agent Core under `/v1/agents/{agent_id}/voice/*`. It unifies voice library listing, prompt-based voice design, audio sample cloning, TTS preview, and voice deletion. ## List voices diff --git a/src/content/docs/reference/overview.mdx b/src/content/docs/reference/overview.mdx index fbda862..a73e4a5 100644 --- a/src/content/docs/reference/overview.mdx +++ b/src/content/docs/reference/overview.mdx @@ -17,22 +17,31 @@ All paths in this reference are relative to the base URL. For example, `GET /v1/ ## Authentication -The API supports two authentication methods, sent via HTTP headers: +Every API request requires authentication via one of these headers: -| Header | Description | +| Header | When to use | |--------|-------------| -| `X-API-Key` | A Personal Access Token (PAT) created in the GEN dashboard or via the [API Keys](/reference/api-keys/) endpoints. | -| `Authorization` | A `Bearer` JWT issued by Clerk or Dynamic XYZ (used by the GEN web app). | - -For programmatic integrations, use `X-API-Key`. For browser-based apps that already authenticate through GEN's login flow, use `Authorization: Bearer `. +| `X-API-Key` | **Recommended for all integrations.** A Personal Access Token (PAT) you create in the GEN dashboard. | +| `Authorization: Bearer ` | Only if your app already uses GEN's browser login flow. | ```bash curl https://api.gen.pro/v1/me \ -H "X-API-Key: your-api-key" ``` +### How to get an API key + +1. Log in to [gen.pro](https://gen.pro) +2. Select an agent (or create one) +3. Go to the **API** page in the left sidebar (`gen.pro/{agent_id}/api`) +4. Click **Create API Key**, give it a name, and copy the token + +That token is your `X-API-Key`. Store it securely — it won't be shown again. + +You can also create and manage API keys programmatically via the [API Keys endpoints](/reference/api-keys/). + ## Agent scoping diff --git a/src/content/docs/reference/rendering.mdx b/src/content/docs/reference/rendering.mdx index 2e2f4de..fd26af4 100644 --- a/src/content/docs/reference/rendering.mdx +++ b/src/content/docs/reference/rendering.mdx @@ -107,7 +107,7 @@ pending → processing → completed | failed | Status | Description | |--------|-------------| -| `pending` | Queued, waiting for Remotion Lambda to pick it up. | +| `pending` | Queued, waiting for the render pipeline to pick it up. | | `processing` | Video is being rendered. | | `completed` | Done. `url` contains the final video CDN link. | | `failed` | Render failed (check logs). | From 6870ade81fcab38fc5a597d0336b01fbdb1309df Mon Sep 17 00:00:00 2001 From: mavneox Date: Sat, 11 Apr 2026 19:47:34 -0500 Subject: [PATCH 3/4] docs(GEN-2755): add new agent-core fields, fix SDK example method name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added look, content_themes, persona_inspirations, timezone, eleven_lab_api_key to GET response example and PATCH field semantics table - Added validation guardrails for new fields (look 2000 chars, timezone 100 chars, eleven_lab_api_key 100 chars, goal 100 chars) - Fixed response description: removed incorrect 207 Multi-Status language; agent-core always returns flat 200 (Rails failures are logged, not surfaced) - Fixed errors table: removed 207 entry, added 403 (wrong agent owner) - Fixed TypeScript SDK example: gen.agents.updateCore() → gen.updateAgentCore() - Updated llms.txt PATCH example with new fields and corrected response shape Co-Authored-By: Claude Opus 4.6 (1M context) --- public/llms.txt | 15 +++++-- src/content/docs/reference/agent-core.mdx | 49 ++++++++++++++--------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index d4f7e18..88ab76d 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -54,12 +54,21 @@ PATCH /v1/agents/{agent_id}/core { "url": "https://tiktok.com/@santiago_sa", "platform": "tiktok" } ], "personality": "Santiago grew up eating at his tia's taco stand in south San Antonio. 28, easy-going, trusts his tongue over reviews.", + "look": "Warm earthy tones, close-up food shots, natural light", + "content_themes": ["street food", "local spots", "food reviews"], + "persona_inspirations": ["@keilapacheco", "@thetacochronicles"], + "timezone": "America/Chicago", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } } ``` -Returns 200 on full success or 207 Multi-Status with per-section results. A single call updates all relevant sections internally. - -**Critical distinction:** `linked_accounts` = the agent's OWN brand socials (what it posts from). `monitored` = inspiration sources (creators/hashtags/keywords to watch for trending content). Don't mix them. Also: `description` is a short 2-3 sentence summary (max 500 chars); full persona text goes in `personality` (max 20000 chars). Stuffing persona into description returns 422. +Returns 200 with the full updated agent core object. Upstream Rails failures (timezone, voice, ElevenLabs key) are logged but don't affect the HTTP status — local fields save first. + +**Field distinctions:** +- `linked_accounts` = the agent's OWN brand socials (what it posts from). `monitored` = inspiration sources (creators/hashtags to watch). +- `description` is a short 2-3 sentence summary (max 500 chars); full persona text goes in `personality` (max 20000 chars). +- `content_themes` = content pillars (e.g. "street food", "travel"). `persona_inspirations` = creator accounts whose style to emulate. +- `look` = visual style description for reference image generation (max 2000 chars). +- `timezone` + `eleven_lab_api_key` = Rails-owned fields forwarded automatically; they appear in GET responses alongside the local brand fields. ### Step 2: Generate content ideas diff --git a/src/content/docs/reference/agent-core.mdx b/src/content/docs/reference/agent-core.mdx index 28edac1..c52b9f9 100644 --- a/src/content/docs/reference/agent-core.mdx +++ b/src/content/docs/reference/agent-core.mdx @@ -68,15 +68,24 @@ Returns every field as a flat object. Fields that haven't been set return `null` "linked_accounts": [ { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago 🌮" } ], + "look": "Warm earthy tones, close-up food shots, natural light", + "content_themes": ["street food", "local spots", "food reviews"], + "persona_inspirations": ["@keilapacheco", "@thetacochronicles"], "personality": "Santiago grew up eating at his tía's taco stand in south San Antonio...", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" - } + }, + "name": "Santiago Real", + "profile_photo_url": "https://cdn.gen.pro/avatars/abc123.jpg", + "timezone": "America/Chicago", + "eleven_lab_api_key": null } ``` +Fields marked with a `*` below are sourced from the Rails agent record and returned alongside the local brand profile fields. If Rails is unreachable, they return `null` — the rest of the response still succeeds. + ## Update the agent setup ``` @@ -89,13 +98,17 @@ Flat merge-patch body. Send only the fields you want to change — omitted field | Field | Semantics | |---|---| -| Scalars (`brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality`) | Replace | +| Scalars (`brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality`, `look`) | Replace | | `keywords` | Replace the full list | | `target_platforms` | Replace the full list | +| `content_themes` | Replace the full list | +| `persona_inspirations` | Replace the full list | | `monitored` | **Replace the full list** — items you don't send are deleted | | `research_topics` | **Replace the full list** — topics you don't send are deleted. Max 20 items. | | `linked_accounts` | **Replace the full list** — rows you don't send are deleted. Rows with `id` are updated in place; rows without `id` are inserted. | -| `default_user_voice` | Replace | +| `default_user_voice` | Replace — forwarded to the Rails agent record | +| `timezone` | Replace — IANA timezone string (e.g. `"America/Chicago"`). Forwarded to the Rails agent record. | +| `eleven_lab_api_key` | Replace — ElevenLabs API key for AURA voice connection. Send `null` to disconnect. Forwarded to the Rails agent record. | To add a single `monitored` item or `linked_account` without touching others, `GET /core` first, append, then `PATCH` with the full list. @@ -104,6 +117,10 @@ To add a single `monitored` item or `linked_account` without touching others, `G - Unknown fields return **422** (`extra="forbid"` on every model). - `description` max 500 chars. - `personality` max 20000 chars. +- `look` max 2000 chars. +- `goal` max 100 chars. +- `timezone` max 100 chars (IANA format, e.g. `"America/Chicago"`). +- `eleven_lab_api_key` max 100 chars. - `identity_type` strict enum: `"brand"` | `"character"`. - `monitored[].item_type` strict enum: `"account"` | `"hashtag"` | `"keyword"`. - `research_topics` max 20 items, each `topic` max 500 chars. @@ -116,17 +133,7 @@ If you send `keywords` without also sending `monitored`, the writer automaticall ### Response -`200` on full success, `207 Multi-Status` on partial failure. The body is a map keyed by **upstream target**, not by request field: - -```json -{ - "brand": { "status": "ok", "data": { "brand_name": "Santiago", "keywords": ["streetfood"], ... } }, - "personality": { "status": "ok", "data": "Santiago grew up eating at..." }, - "voice": { "status": "error", "error": "voice service unavailable" } -} -``` - -Sections only appear if the PATCH touched at least one field they own. A PATCH that only sets `brand_name` returns `{ "brand": { "status": "ok", ... } }` — no `personality` or `voice` entry. +`200` with the full updated agent core object (same shape as `GET /core`). If an upstream Rails write fails (e.g. saving `timezone` or `eleven_lab_api_key` to the Rails agent record), the failure is logged but does **not** affect the HTTP status — local fields are always saved first. ## Example — set up a character in one call @@ -156,6 +163,10 @@ curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], "personality": "Santiago grew up eating at his tía'\''s taco stand in south San Antonio. He'\''s 28, easy-going, trusts his tongue over reviews, and will fight anyone who says Austin has better tacos.", + "look": "Warm earthy tones, close-up food shots, natural light", + "content_themes": ["street food", "local spots", "food reviews"], + "persona_inspirations": ["@keilapacheco", "@thetacochronicles"], + "timezone": "America/Chicago", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } }' ``` @@ -167,7 +178,7 @@ import { GenClient } from '@poweredbygen/gen-sdk'; const gen = new GenClient({ apiKey: process.env.GEN_API_KEY! }); -const result = await gen.agents.updateCore('abc123', { +const result = await gen.updateAgentCore('abc123', { brand_name: 'Santiago', description: 'San Antonio street food scout who hunts the best tacos.', identity_type: 'character', @@ -189,9 +200,7 @@ const result = await gen.agents.updateCore('abc123', { default_user_voice: { voice_id: '21m00Tcm4TlvDq8ikWAM', source: 'public' }, }); -if (result.personality?.status === 'error') { - console.error('personality write failed:', result.personality.error); -} +console.log(result.agent_id, result.brand_name); ``` ## Errors @@ -199,9 +208,9 @@ if (result.personality?.status === 'error') { | Code | Meaning | |---|---| | 401 | Missing or invalid `X-API-Key` | -| 404 | Agent not found | +| 403 | Valid PAT but the agent belongs to a different user | +| 404 | Agent config not found | | 422 | Validation error — unknown field, bad enum, length cap exceeded, or missing required sub-field like `monitored[].item_type` | -| 207 | Partial success. Inspect per-target `status` in the response body. | ## Related From c3467b3a0610dbaffb071b86bd8ed8a2ce84c790 Mon Sep 17 00:00:00 2001 From: mavneox Date: Sun, 12 Apr 2026 17:16:39 -0500 Subject: [PATCH 4/4] docs(GEN-2604): add look field to agent-core llms-full.txt - Add `look` to GET /core response example - Add `look` to the scalars-replace list in PATCH semantics - Add `look` max 2000 chars validation rule - Add `look` to PATCH curl example Co-Authored-By: Claude Opus 4.6 (1M context) --- public/llms-full.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/llms-full.txt b/public/llms-full.txt index a3178b9..422f1bf 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -490,6 +490,7 @@ Response: "linked_accounts": [ { "id": 42, "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], + "look": "Warm earthy tones, close-up street food shots, natural light. Mid-20s Latino man, often seen on bustling sidewalks with a taco in hand.", "personality": "Santiago grew up eating at his tia's taco stand in south San Antonio...", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "source": "public" } } @@ -500,7 +501,7 @@ Response: Flat merge-patch. Send only the fields you want to change. Unknown fields return 422. **Field-level semantics:** -- Scalars replace: `brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `personality` +- Scalars replace: `brand_name`, `description`, `identity_type`, `goal`, `shortform`, `longform`, `onboarding_status`, `look`, `personality` - List fields use FULL replacement (missing = deleted): `keywords`, `target_platforms`, `monitored`, `research_topics`, `linked_accounts` - `linked_accounts` diffing: rows with `id` are updated in place, rows without `id` are inserted, rows absent from the list are deleted. To add one row without touching others, GET first, append, PATCH. - `default_user_voice` replaces @@ -509,6 +510,7 @@ Flat merge-patch. Send only the fields you want to change. Unknown fields return **Validation:** - `description` max 500 chars +- `look` max 2000 chars - `personality` max 20000 chars - `identity_type` enum: `"brand"` | `"character"` - `monitored[].item_type` enum: `"account"` | `"hashtag"` | `"keyword"` @@ -549,6 +551,7 @@ curl -X PATCH https://api.gen.pro/v1/agents/abc123/core \ "linked_accounts": [ { "url": "https://tiktok.com/@santiago_real", "platform": "tiktok", "display_name": "Santiago" } ], + "look": "Warm earthy tones, close-up street food shots, natural light. Mid-20s Latino man, often seen on bustling sidewalks with a taco in hand.", "personality": "Santiago grew up eating at his tia\u0027s taco stand in south San Antonio. 28, easy-going, trusts his tongue over reviews, and will fight anyone who says Austin has better tacos.", "default_user_voice": { "voice_id": "21m00Tcm4TlvDq8ikWAM", "source": "public" } }'