WinsPipe Voice Engine

Documentation

Voice Engine

The Voice Engine turns a workspace’s voices, a purpose-built engine, and a brief into a draft the author can edit, then scores that draft against the same material. Profiles hold the voice. Engines decide which voices apply. Facts, phrase rules, feeds, and X lists supply the evidence and the constraints.

How the Voice Engine works

Every tenant shares the same structure. The words inside it belong to the workspace. There is no built-in list of companies, people, competitors, or industry terms. A profile with no published version is absent from drafting and checking.

Command Center

The Voice section at /voice is the editor: explorer, Inbox, profiles, engines, facts, and the prompt composer. Humans publish versions here.

Write

Draft from a brief and run a voice check beside the composer. A failed check warns. Saving still works. Scheduling past a failure is an explicit acknowledgement.

HTTP API

Every capability on this page is a route under /api/v1/voice. The product UI and an agent key call the same handlers. Nothing here schedules or publishes a post.

Worker

Ingestion, transcription, stance briefs, intake, archive parsing, feed polls, X list sync, and X compliance run as jobs. The API enqueues them and reads the result.

Concepts

Profiles

A profile is a brand, a personal voice, a persona, or a lens. A workspace has one brand profile. Each publishing human has one personal profile. Personas are many: a person, an institution, or a group. Influence is align (“stay on message”) or emulate (“sound like them”). Align contributes beliefs, claim policy, pillars, audience, and attributed examples. Emulate also contributes style, playbooks, and lexicon terms the persona prefers. Neither permits verbatim reuse.

Sections are a fixed taxonomy: charter, style, beliefs, audience, playbooks, pillars, lexicon, claim policy, negative examples, aligned examples, and network. Prose sections store a body. Lexicon, examples, and network store rows. A lens may hold only the topic sections: beliefs, audience, pillars, lexicon, playbooks, claim policy, both example tables, and network. Charter and style stay on the personal voice.

Edits land on a working copy. Publishing snapshots that copy, the identity card, and the persona influence into an immutable version. Drafting and checking read the latest published version. Restoring a version copies it back into the working draft and leaves history intact.

Engines

An engine is a named recipe for one purpose: which author (self or a personal profile), which lens, which personas at which influence, whether the brand applies, engine instructions, routing keywords and tags, platforms, prompt blocks, example pins, and fact settings. General is built in. Its id is general, it has no config of its own, and it uses the workspace’s published personal voice, personas, and brand.

Phrasing precedence is voice memory, then personal, then emulated personas, then aligned personas, then brand. When the engine has a lens, the lens outranks the core personal voice. Claim policy and lexicon use the stricter rule. Members can lock a persona to a specific published version. Otherwise the latest publish is used.

Prompt

Block mode sends an ordered plan: identity, style, beliefs, examples, facts, phrase rules, platform playbook, and the engine’s instructions. Three blocks stay on and cannot be budgeted: rules.core, guardrails.phrases, and platform. Raw mode replaces that plan with a template of up to 20,000 characters. Slots look like {{charter}} and are filled from the published profiles at draft time. A detached slot is literal text the author pasted over a slot; the editor warns when that profile is republished. The floor stays outside the template: fenced third-party text is data, the output contract stays fixed, and the phrase-rule pass still runs.

Facts, rules, and memory

A fact is a claim with tags, a citation, and an optional expiry, attached to a brand or persona profile. A person who creates one approves it immediately. An agent key can only propose. Drafts see approved facts that have not expired. An engine chooses tags, pins up to 24 facts, and caps how many are offered (default 5, at most 12).

A phrase rule bans a phrase and optionally supplies up to three replacements. Scope is the author’s drafts, one engine, or the whole workspace. People create active rules. Agents propose, and a person accepts. Matching ignores case, treats hyphens and whitespace runs as equal, and respects word boundaries. The post-generation pass rewrites a hit when a replacement exists, and the voice check flags whatever remains.

Voice memory is a short list of preferred and avoided spans learned when a caption is saved in Write. The assembler reads it. This API does not write it. GET /api/v1/voice returns the same object the context embeds.

Objects

Profile

The durable voice: sections, identity, and published versions.

  • Types: brand, personal, persona, lens
  • Persona kinds: person, institution, group
  • Working copy until a human publishes
  • Markdown export that round-trips with replace
Engine

A versioned recipe that picks voices and a prompt for one purpose.

  • Up to 24 per owner, 12 persona members
  • Block mode or a raw slot template
  • Playground trials compare draft and published
  • General stays available with id general
Fact

An approved claim a draft may use as evidence.

  • Status: proposed, approved, retired
  • Expiry removes it from retrieval until renewed
  • 500 characters, 8 tags, citation required in spirit
  • 2,000 facts per workspace
Phrase rule

A live ban that applies on the next draft without a publish.

  • Scopes: author, engine, workspace
  • Up to 3 replacements, 120-character phrase
  • Word-bounded, case-insensitive matching
  • 500 rules per workspace
Source and feed

Material a person reviews before it becomes voice.

  • Link, upload, or paste, then a stance brief
  • RSS, podcast, or YouTube channel
  • 12 feeds per profile, poll at least hourly
  • New items arrive in the Inbox
X list

A workspace’s own X app, used as an engine input.

  • Bearer token, optional OAuth app, optional user token
  • Estimate shown before the first attach
  • Circle, radar digest, or a group persona
  • Daily read cap, 7-day post cache, compliance purge

Choose a path

Browse by capability

Authentication

Send a Clerk session cookie from the signed-in app, or an agent key:

Authorization: Bearer stories_…

Agent keys match stories_ plus 32 URL-safe characters. A key carries scopes. read covers context, plans, lists, and checks that are marked Read. write covers creates and edits. A human session has every scope. Routes marked Human reject every agent key with privileged_human_required, including publish, engine create and delete, phrase-rule edits, fact approval, archive import, and X credentials.

Browser calls from the app must be same-origin. Responses send Cache-Control: private, no-store. The API rate limit is 120 requests per actor per minute. Unauthenticated calls are limited to 60 per IP per minute.

Errors

Failures return JSON. The message is safe to show. Provider response bodies are not copied through.

StatusCodeWhen
400invalid_inputA required field is missing or a value fails validation. The message says which rule.
401unauthorizedNo session, or the agent key is invalid, revoked, or belongs to a deleted workspace.
403forbiddenThe key is missing the read or write scope the route requires.
403privileged_human_requiredThe route is human-only and the caller is an agent key.
404not_foundThe id is missing, or a lens is hidden from this human.
409conflictThe brief was already reviewed, or this engine already follows that X list.
400not_configuredAn X call needs a bearer token that has not been saved.
400invalid_credentialsX rejected the stored credentials. The token is not echoed.
402out_of_creditThe X app is out of credit. Sync pauses until it can read again.
429rate_limitedThe actor or X is over a cap. Retry-After is set, in seconds.
500llm_failedThe model call failed. The public message does not include the provider error. No draft is created.
500internal_errorAnything else. The message is generic.

Shape: { "code": "invalid_input", "message": "A brief needs a subject." }

Limits

LimitValue
API requests120 per actor per minute
Drafts30 per actor per hour
Voice checks120 per actor per hour
Playground trials20 per actor per hour, one or two variants each
Briefs, intake, source writes, X identity reads30 per actor per hour
Engines per owner24
Persona members per engine12
Facts per workspace2,000
Phrase rules per workspace500
Feeds per profile12, polled between every hour and every 7 days
X daily readsDefault 2,000, configurable from 10 to 100,000
X list membersDefault cap 500, maximum 5,000
Radar posts kept100 reads per sync, stored 7 days
Markdown import64 files, 2 MB

Background jobs

These run on the worker. The HTTP routes above enqueue them and return the job id. Poll the resource, or watch the Inbox, for the result.

KindWhat it does
voice_ingestFetch a link or file, classify it, and store text. Moves the source through pending, fetching, transcribing, ready, needs_text, or failed.
voice_transcribeSpeech-to-text for audio and video, under per-source and per-workspace caps.
voice_briefPropose section items, examples, lexicon rows, and facts from a transcript. A person merges or rejects.
voice_intakeDraft working sections from a site, sample posts, and leader sources.
voice_archive_importParse an X archive or LinkedIn export into a reviewable report.
voice_feed_pollRead a due RSS, podcast, or YouTube feed and create a source per new item, at most 10.
voice_x_list_syncRefresh an attached list: circle members, radar posts, and the optional group persona.
voice_x_complianceDelete stored posts that X no longer serves. Runs on the compliance schedule, within 24 hours of a deletion on X.

The reference below is every route under /api/v1/voice.

Voice memory

Spans learned from caption edits. The assembler reads them; this route does not write them.

GETRead voice memory

/api/v1/voice

The preferred and avoided spans Write learned from caption edits, plus a short context note. The same object is embedded in the assembled context.

Read A signed-in session, or any agent key.

200. VoiceMemoryDTO: preferred and avoided string lists (at most 18 each), context, and updatedAt.

Context and prompt

The published voices, the prompt plan, and a preview that does not call a model.

GETAssemble voice context

/api/v1/voice/context

Published profiles for one engine, voice memory, and the precedence order the drafter and the checker both use. Omit engineId, or pass general, for the built-in General engine.

Read A signed-in session, or any agent key.

Query parameters

engineIdstringOptional

An engine id, or general. Absent means General.

200. VoiceContextDTO: precedence, personal, personas, brand, optional lens, engine, memory, hasProfiles, generatedAt.

  • A profile with no published version is absent. Missing sections are omitted rather than filled with defaults.
  • Phrasing precedence is voice memory, then personal, then emulated personas, then aligned personas, then brand. On a lens engine the lens outranks the core personal voice.

GETRead the prompt plan

/api/v1/voice/plan

The deterministic system prompt for one engine and platform: every block, its provenance, character budget, and the rendered text. No model call.

Read A signed-in session, or any agent key.

Query parameters

engineIdstringOptional

Engine id. Absent or general uses General.

platform"x" | "linkedin"Optional

Platform playbook to include. Anything other than linkedin is treated as x.

themestringOptional

Stand-in for a brief subject, up to 500 characters, so example retrieval can be previewed.

draft"1"Optional

When 1, preview the engine’s unpublished working settings.

200. plan (PromptPlan), system (rendered string), hasProfiles, candidates (ranked aligned examples), defaultTemplate (raw mode’s starting template).

  • Locked blocks rules.core, guardrails.phrases, and platform stay on in block mode and cannot be budgeted.
  • Raw mode still keeps a fixed floor: fenced third-party text, the output contract, and the post-generation phrase pass.

POSTPreview unsaved prompt settings

/api/v1/voice/plan

Validate a config the way a save would, then return the prompt plan for it. Nothing is stored. Read access is enough.

Read A signed-in session, or any agent key.

Body

engineIdstringRequired

A saved engine. general is rejected.

configVoiceEngineConfigRequired

Unsaved settings. Unknown keys are dropped.

platform"x" | "linkedin"Optional

Defaults to x.

themestringOptional

Up to 500 characters.

200. The same object as GET /plan.

Drafts and checks

Turn a brief into an editable draft, then score that draft against the same engine.

POSTDraft from a brief

/api/v1/voice/drafts

Create an editable draft post from a content brief. The copy lands in Drafts with generation metadata. This route never schedules or publishes.

Write A signed-in session, or an agent key with the write scope.

Body

subject{ kind, text, url? }Required

kind is link, transcript, or idea. text is required, up to 2,000 characters.

claimsBriefClaim[]Required

At least one evidence claim. Each has text, source, and an optional factId. At most 20.

anglestringOptional

Optional point of view, up to 1,000 characters.

platforms("x" | "linkedin")[]Required

At least one platform.

constraints{ maxPosts?, thread? }Optional

Thread shape and length hints passed to the model.

engineIdstringOptional

Engine to draft with. Absent or general uses General.

mediaAssetIduuidOptional

Media to attach, such as brand-approved media offered with a fact.

201. post (the draft), notes (editorial notes), generation (model, prompt hash, profile versions, example ids, engine, phrase-rule hits, fact ids).

  • Limited to 30 drafts per actor per hour. A 429 includes Retry-After.
  • Active phrase rules are applied after generation. Hits are recorded on generation.phraseRuleHits.
  • A failed model call returns llm_failed and does not create a post.

POSTRun a voice check

/api/v1/voice/check

Score the current copy of a draft on six rubric dimensions and run deterministic triggers (banned phrases, avoid terms, unverified handles, register). A failed check warns. It does not block save. Scheduling past it is a separate human acknowledgement in Write.

Write A signed-in session, or an agent key with the write scope.

Body

postIduuidRequired

The draft to score.

engineIdstringOptional

Engine to check against. Absent uses the engine stored on the draft, then General.

201. VoiceCheckDTO: id, postId, engine, optional engineNote, rawPrompt, passed, scores per platform, triggers, model, profileVersionIds, createdAt.

  • A check fails when any trigger fires or any dimension scores below 3.
  • Dimensions are register, argument, grounding, platformFit, restraint, and novelty, each an integer from 1 to 5.
  • Limited to 120 checks per actor per hour.

Profiles

Brand, personal, persona, and lens voices: working sections, identity, versions, and Markdown.

GETList profiles

/api/v1/voice/profiles

Every voice profile this caller can see. Lenses are visible to their owner and to agent keys. Other types are workspace-wide.

Read A signed-in session, or any agent key.

200. { workspaceId, profiles: VoiceProfileSummary[] }.

POSTCreate a profile

/api/v1/voice/profiles

Create a brand, persona, or personal voice. A workspace has one brand profile and one personal profile per publishing human. Lenses are created from an engine, not here.

Write A signed-in session, or an agent key with the write scope.

Body

type"brand" | "persona" | "personal"Required

Profile type.

displayNamestringRequired

Name shown in the explorer.

relationshipNotestringOptional

Why this persona is in the workspace. Institutions and groups may omit it.

influence"align" | "emulate"Optional

Personas only. align stays on message. emulate also picks up cadence. Defaults to align.

personaKind"person" | "institution" | "group"Optional

Personas only. Defaults to person.

201. VoiceProfileDTO, including empty working sections.

GETGet the personal or brand profile

/api/v1/voice/profiles/by-type/{type}

The workspace’s single personal or brand profile, for the type-based Voice routes. Personas are many; list them with GET /profiles.

Read A signed-in session, or any agent key.

Path parameters

type"personal" | "brand"Required

Any other value is invalid_input.

200. VoiceProfileDTO. 404 when that type has not been created yet.

GETGet a profile

/api/v1/voice/profiles/{id}

Working sections, identity card, influence, and the current published version.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Profile id.

200. VoiceProfileDTO.

PATCHUpdate profile metadata

/api/v1/voice/profiles/{id}

Rename a profile or change a persona’s relationship note, influence, or kind. Section text is saved with the section route.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

Body

displayNamestringOptional

New name.

relationshipNotestringOptional

Persona relationship note.

influence"align" | "emulate"Optional

Persona influence. Takes effect on the next publish.

personaKind"person" | "institution" | "group"Optional

What the persona represents.

200. VoiceProfileDTO.

DELETEDelete a profile

/api/v1/voice/profiles/{id}

Remove a profile from the workspace. Published history goes with it.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

200. { ok: true }.

PUTSave a section

/api/v1/voice/profiles/{id}/sections/{section}

Replace one working section. Drafting keeps using the published version until a person publishes.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

sectionVoiceSectionRequired

charter, style, beliefs, audience, playbooks, pillars, lexicon, claim_policy, negative_examples, aligned_examples, or network. A lens may only hold beliefs, audience, pillars, lexicon, playbooks, claim_policy, negative_examples, aligned_examples, and network.

Body

bodystringOptional

Prose for a non-tabular section.

rowsVoiceRow[]Optional

Table rows. Lexicon uses prefer, avoid, why. Examples use text, source, tag, attribution, platform. Network uses handle, platform, name, relationship, note.

unmappedUnmappedChunk[]Optional

Import leftovers kept beside the section.

200. The saved VoiceSectionDTO.

POSTPublish a profile version

/api/v1/voice/profiles/{id}/publish

Snapshot the working sections, identity, and persona influence into an immutable version. Agents can edit the working copy. Only a person can publish.

Human A signed-in human session. An agent key receives 403 privileged_human_required. Also requires the write scope when the caller is an agent key, which this route rejects before that check.

Path parameters

iduuidRequired

Profile id.

Body

changelogstringOptional

What changed, stored on the version.

201. VoiceVersionDTO: id, number, publishedAt, changelog, profileId, publishedBy, sections, influence, identity.

GETList profile versions

/api/v1/voice/profiles/{id}/versions

Immutable publishes, newest last in the array the service returns.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Profile id.

200. { versions: VoiceVersionRef[] } with id, number, publishedAt, changelog.

GETGet a profile version

/api/v1/voice/profiles/{id}/versions/{versionId}

One published snapshot, including sections and the identity card as they were.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Profile id.

versionIduuidRequired

Version id.

200. VoiceVersionDTO.

POSTRestore a profile version

/api/v1/voice/profiles/{id}/versions/{versionId}/restore

Copy a published snapshot into the working draft. History is unchanged until someone publishes again.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

versionIduuidRequired

Version to copy.

200. VoiceProfileDTO with the restored working sections.

GETExport a profile as Markdown

/api/v1/voice/profiles/{id}/export

One Markdown file per filled section. Importing those files back with replace: true restores the working draft.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Profile id.

200. { files: [{ name, content }] }.

POSTImport a Markdown bundle

/api/v1/voice/profiles/{id}/import

Map headings and tables onto working sections. The import never publishes. With replace, the bundle overwrites those sections so an export round-trips.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

Body

files{ name, content }[]Required

At most 64 files and 2 MB.

replacebooleanOptional

When true, imported sections replace the working copy instead of merging.

200. Import result: mapped sections and any unmapped chunks.

PUTSave the identity card

/api/v1/voice/profiles/{id}/identity

Replace the working identity card: role, organization, industry, location, bio, website, expertise, interests, handles, and a one-paragraph network summary. Drafting reads it after publish.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

Body

identityVoiceIdentityRequired

Text fields are at most 120 characters (bio 500, website 200, network 300). Lists hold at most 20 items. At most 10 handles.

200. { identity, profile }.

POSTPropose identity from X

/api/v1/voice/profiles/{id}/identity/from-x

Read the workspace’s connected X account and return a proposed identity card. Nothing is saved until PUT /identity.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Profile id. A lens is rejected; it takes identity from the personal voice.

200. { handle, proposal: VoiceIdentity, changes: field names that differ from the working card }.

  • Requires a connected X account in Settings. Limited to 30 calls per actor per hour.
  • A lens returns invalid_input.

GETSuggest posted examples

/api/v1/voice/profiles/{id}/posted

Posted copy ranked by official metrics that is not yet an aligned example on this profile.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Profile id.

Query parameters

limitnumberOptional

How many suggestions. Defaults to 10.

200. { suggestions: PostedSuggestion[] } with id, postId, platform, text, publishedAt, and likes.

POSTAdd posted examples

/api/v1/voice/profiles/{id}/posted

Copy the picked posts onto the working aligned-examples section.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Profile id.

Body

idsstring[]Required

Suggestion ids from GET /posted.

200. The updated profile section state from the collector.

Engines

Named recipes that choose an author, a lens, personas, brand, and a prompt.

GETList engines

/api/v1/voice/engines

General first, then the caller’s engines. Agent keys see every engine in the workspace.

Read A signed-in session, or any agent key.

200. { engines: VoiceEngineSummary[] }. General has id general and builtin true.

POSTCreate an engine

/api/v1/voice/engines

An engine belongs to the person who creates it. Names cannot be General. At most 24 engines per owner.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Body

namestringRequired

Up to 60 characters. A URL-safe slug is derived from it.

descriptionstringOptional

Up to 300 characters.

configVoiceEngineConfigOptional

Author, members, brand, instructions, routing, platforms, blocks, examples, facts, and prompt mode. Defaults are used for anything omitted.

withLensbooleanOptional

When true, also create the topic lens.

201. VoiceEngineDTO: summary plus config and publishedConfig.

  • VoiceEngineConfig.author is self or a personal profile id. members hold at most 12 personas, each align, emulate, or off. instructions are at most 2,000 characters. routing keywords and tags are at most 20 items of 40 characters.

GETGet an engine

/api/v1/voice/engines/{id}

Working config and the last published config.

Read A signed-in session, or any agent key.

Path parameters

idstringRequired

Engine id. general is the built-in engine.

200. VoiceEngineDTO.

PATCHSave engine settings

/api/v1/voice/engines/{id}

Update the working draft. An agent key may edit settings. Only a person can rename or publish.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Engine id.

Body

namestringOptional

Human callers only.

descriptionstringOptional

Up to 300 characters.

configVoiceEngineConfigOptional

Full settings object. Block budgets are 200 to 12,000 characters. Raw templates are at most 20,000 characters and may contain {{slot}} placeholders.

200. VoiceEngineDTO.

DELETEDelete an engine

/api/v1/voice/engines/{id}

Remove an engine the caller owns. General cannot be deleted.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Engine id.

200. { ok: true }.

POSTAdd a lens

/api/v1/voice/engines/{id}/lens

Create the topic lens for this engine. The call is idempotent when the lens already exists. The circle for an X list is stored on this lens.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Engine id.

201. VoiceEngineDTO including the lens summary.

POSTPublish an engine

/api/v1/voice/engines/{id}/publish

Snapshot the working config. Drafts use the latest published version unless a member is locked to an older profile version.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Engine id.

Body

changelogstringOptional

What changed.

201. The published version.

GETList engine versions

/api/v1/voice/engines/{id}/versions

Published configs for this engine, including the changelog and the settings snapshot for each version.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Engine id.

200. { versions } with id, number, publishedAt, changelog, and config.

POSTRestore an engine version

/api/v1/voice/engines/{id}/versions/{versionId}/restore

Copy a version’s settings into the working draft. It does not rewrite history.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Engine id.

versionIduuidRequired

Version id.

200. VoiceEngineDTO.

GETList playground trials

/api/v1/voice/engines/{id}/trials

The engine’s Playground log, newest first. Trials are samples. They are not posts.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Engine id.

200. { trials: VoiceTrialDTO[] }.

POSTRun a playground trial

/api/v1/voice/engines/{id}/trials

Draft a sample brief with one or two variants of the engine and voice-check each. Variants are draft, published, or a version id. No post is created.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Engine id.

Body

briefContentBriefRequired

Same shape as POST /drafts, without engineId and mediaAssetId.

variantsstring[]Optional

One or two of draft, published, or a version uuid. Defaults to [draft].

201. The trial run: each variant’s copy, editorial notes, and voice-check result.

  • Limited to 20 trials per actor per hour. Each variant spends a model draft and a check.

Phrase rules

Live “never use” and “use instead” rules that apply on the next draft without a publish.

GETList phrase rules

/api/v1/voice/phrase-rules

Rules this caller can see, plus suggestions built from phrases they keep deleting.

Read A signed-in session, or any agent key.

200. { rules: PhraseRuleDTO[], suggestions: PhraseRuleSuggestion[] }.

POSTCreate a phrase rule

/api/v1/voice/phrase-rules

A person creates an active rule. An agent key can only propose one, and a person accepts it before it applies. Matching is case-insensitive and word-bounded.

Write A signed-in session, or an agent key with the write scope.

Body

phrasestringRequired

Up to 120 characters.

alternativesstring[]Optional

Up to 3 replacements, each at most 80 characters.

reasonstringOptional

Up to 200 characters.

avoidSimilarbooleanOptional

Also flag close variants.

scope"author" | "engine" | "workspace"Optional

All my drafts, this engine only, or everyone in the workspace.

engineIduuidOptional

Required when scope is engine.

createdFrom"manual" | "post" | "check" | "trial" | "memory"Optional

Where the rule was born. Defaults to manual.

201. PhraseRuleDTO.

  • At most 500 rules per workspace.

PATCHUpdate a phrase rule

/api/v1/voice/phrase-rules/{id}

Edit the phrase, replacements, scope, or status. Status may become active or disabled. proposed is only set by an agent create.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Rule id.

Body

phrasestringOptional

Banned phrase.

alternativesstring[]Optional

Replacements.

reasonstringOptional

Why it is banned.

avoidSimilarbooleanOptional

Match close variants.

scope"author" | "engine" | "workspace"Optional

Who the rule applies to.

engineIduuidOptional

Engine when scope is engine.

status"active" | "disabled"Optional

Turn the rule on or off.

200. PhraseRuleDTO.

DELETEDelete a phrase rule

/api/v1/voice/phrase-rules/{id}

Remove the rule. Past drafts are unchanged.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Rule id.

200. { ok: true }.

POSTAccept a proposed rule

/api/v1/voice/phrase-rules/{id}/accept

Activate a rule an agent proposed. It starts applying on the next draft.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Rule id.

200. PhraseRuleDTO with status active.

POSTMove a rule into a lexicon

/api/v1/voice/phrase-rules/{id}/move-to-lexicon

Copy the rule onto a profile’s working lexicon and disable the live rule. Publish the profile for drafting to pick up the lexicon row.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Rule id.

Body

profileIduuidRequired

Profile whose lexicon receives the row.

200. The disabled rule and the lexicon write.

POSTDismiss a phrase suggestion

/api/v1/voice/phrase-rules/suggestions/dismiss

Stop suggesting a rule for a phrase the author keeps removing.

Write A signed-in session, or an agent key with the write scope.

Body

phrasestringRequired

The suggested phrase, truncated at 200 characters.

200. { ok: true }.

Brand facts

Approved claims with a source and an optional expiry, offered to engines as evidence.

GETList facts

/api/v1/voice/facts

Facts on brand and persona profiles, plus counts for the explorer. At most 500 rows.

Read A signed-in session, or any agent key.

Query parameters

profileIduuidOptional

Limit to one profile.

status"proposed" | "approved" | "retired" | "expired"Optional

expired means approved and past expiresAt.

tagstringOptional

One tag.

200. { facts: VoiceFactDTO[], counts: { approved, proposed, expired } }.

POSTAdd a fact

/api/v1/voice/facts

A person’s fact is approved on save. An agent key can only propose one. Drafts use a fact after it is approved and before it expires.

Write A signed-in session, or an agent key with the write scope.

Body

profileIduuidRequired

A brand or persona profile.

textstringRequired

The claim, up to 500 characters.

tagsstring[] | stringOptional

Up to 8 tags, 40 characters each. A string is split on commas.

citationstringOptional

Where the claim came from, up to 300 characters.

expiresAtstring | nullOptional

A date (2027-01-31) or an ISO timestamp. Empty means no expiry.

sourceIduuid | nullOptional

Voice source this fact was taken from.

201. VoiceFactDTO.

  • At most 2,000 facts per workspace.

GETGet a fact

/api/v1/voice/facts/{id}

One fact, including whether an approved fact is past its expiry.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Fact id.

200. VoiceFactDTO.

PATCHEdit a fact

/api/v1/voice/facts/{id}

Change text, tags, citation, or expiry. Approved facts reach drafts as written, so this is human-only.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Fact id.

Body

textstringOptional

Replacement claim.

tagsstring[] | stringOptional

Replacement tags.

citationstringOptional

Replacement citation.

expiresAtstring | nullOptional

New expiry, or null to clear it.

200. VoiceFactDTO.

DELETEDelete a fact

/api/v1/voice/facts/{id}

Remove the fact from the library. Drafts stop seeing it immediately, and the row is not kept for the record.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Fact id.

200. { ok: true }.

POSTApprove a fact

/api/v1/voice/facts/{id}/approve

Approve a proposal, reinstate a retired fact, or renew an expired one.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Fact id.

Body

expiresAtstring | nullOptional

Optional new expiry applied as part of the approval.

200. VoiceFactDTO with status approved.

POSTRetire a fact

/api/v1/voice/facts/{id}/retire

Keep the fact in the library and stop every draft from using it.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Fact id.

200. VoiceFactDTO with status retired.

GETSuggest facts for a brief

/api/v1/voice/facts/suggest

Approved, current facts that match the engine and the brief text, plus brand-approved media tagged like them. Nothing is added to a brief until the author picks it.

Read A signed-in session, or any agent key.

Query parameters

engineIdstringOptional

Engine whose fact settings (tags, pins, max) apply. Absent uses General.

qstringOptional

Subject and angle text, up to 1,000 characters.

200. { facts: RetrievedFact[], media: ApprovedMediaDTO[] }.

  • An engine offers at most max facts (default 5, hard cap 12) after pins. Pins are at most 24.

Sources and briefs

Links, uploads, and pastes, then the stance brief a person reviews before anything merges.

GETList sources

/api/v1/voice/sources

Sources in the library, optionally filtered by profile, tag, or collection.

Read A signed-in session, or any agent key.

Query parameters

profileIduuidOptional

One profile.

tagstringOptional

One tag.

collectionIduuidOptional

One collection.

200. { sources: VoiceSourceDTO[] }.

POSTAdd a source

/api/v1/voice/sources

Register a link, an uploaded file (media created with purpose voice_source), or pasted text. Links and files enqueue voice_ingest. Pasted text can go straight to a brief.

Write A signed-in session, or an agent key with the write scope.

Body

profileIduuidRequired

Profile this source informs.

titlestringOptional

Library title.

originUrlstringOptional

http(s) link. url is accepted as an alias.

mediaAssetIduuid | nullOptional

Uploaded file.

transcriptstringOptional

Pasted text. When present, ingestion can skip the fetch.

speakerstringOptional

Who is speaking.

sourceDatestringOptional

When the source was published.

tagsstring[]Optional

Up to 12 tags, 32 characters each.

collectionIduuid | nullOptional

Library collection.

metadataobjectOptional

Optional title, author, siteName, canonicalUrl, publishedAt, language, durationSeconds, pages, platform.

201. VoiceSourceDTO plus job when ingestion was queued.

  • Status moves pending → fetching → transcribing → ready, or needs_text, or failed. After a brief it becomes briefed, then merged.
  • Kinds are link, post, article, video, podcast, document, audio, and paste.
  • Limited to 30 source writes per actor per hour.

GETGet a source

/api/v1/voice/sources/{id}

Transcript, ingest status, metadata, and tags.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Source id.

200. VoiceSourceDTO.

PATCHUpdate a source

/api/v1/voice/sources/{id}

Edit title, link, speaker, date, transcript, tags, collection, or metadata. profileId cannot be changed here.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Source id.

Body

titlestringOptional

Library title.

originUrlstringOptional

Replacement link.

transcriptstringOptional

Replacement text.

speakerstringOptional

Speaker.

sourceDatestringOptional

Source date.

tagsstring[]Optional

Replacement tags.

collectionIduuid | nullOptional

Move between collections.

200. VoiceSourceDTO.

DELETEDelete a source

/api/v1/voice/sources/{id}

Remove the source. Items already merged into a profile are marked unsourced rather than deleted from the section.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Source id.

200. { ok: true }.

POSTRetry ingestion

/api/v1/voice/sources/{id}/retry

Reset a failed or stuck source and enqueue voice_ingest again.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Source id.

202. The reset source plus the job.

POSTRequest a stance brief

/api/v1/voice/sources/{id}/analyze

Queue analysis for a source that already has text. If a pending brief exists, that brief is returned instead of a second job.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Source id.

202. { job } or { brief, deduped: true }.

  • 400 invalid_input when the source has no transcript yet.

GETList stance briefs

/api/v1/voice/briefs

Proposals waiting for review, optionally for one source.

Read A signed-in session, or any agent key.

Query parameters

sourceIduuidOptional

Limit to one source.

200. { briefs: StanceBriefDTO[] }. reviewStatus is pending, merged, or rejected.

POSTQueue a stance brief

/api/v1/voice/briefs

Enqueue analysis of a source. The worker transcribes attached media first when speech-to-text is available, then proposes section items with confidence and citations. Nothing auto-publishes.

Write A signed-in session, or an agent key with the write scope.

Body

sourceIduuidRequired

Source to analyze.

202. { job, hint } or { brief, deduped: true } when a pending brief already exists.

  • Limited to 30 briefs per actor per hour.

GETGet a stance brief

/api/v1/voice/briefs/{id}

The proposal: section items, example rows, lexicon rows, and fact candidates.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Brief id.

200. StanceBriefDTO.

POSTMerge a stance brief

/api/v1/voice/briefs/{id}/merge

Write the accepted items into the working draft, and accepted facts into the library. A person’s facts are approved. An agent’s facts stay proposed. Publishing the profile is still a separate human step.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Brief id.

Body

items{ section, text }[]Optional

Section paragraphs to merge.

examples{ text, attribution?, platform?, polarity? }[]Optional

polarity is aligned or negative.

lexicon{ prefer?, avoid?, why? }[]Optional

Lexicon rows.

facts{ text?, tags?, citation? }[]Optional

Fact candidates to keep.

200. The brief with reviewStatus merged.

  • 409 conflict when the brief is already merged or rejected.

POSTReject a stance brief

/api/v1/voice/briefs/{id}/reject

Discard the proposal. The source stays in the library.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Brief id.

200. The brief with reviewStatus rejected.

POSTRun brand intake

/api/v1/voice/intake

Enqueue a worker run that drafts working sections from a site, sample posts, and leader sources. The result is a working draft, never a publish.

Write A signed-in session, or an agent key with the write scope.

Body

profileIduuidRequired

Profile to fill.

siteUrlstringOptional

Public site to read.

postUrlsstring[]Optional

Sample post links.

leaderSourceIdsuuid[]Optional

Existing sources to treat as leaders.

202. { job: { id, status } }. Retries with the same material share an idempotency key.

  • Limited to 30 intake runs per actor per hour.

Feeds

RSS, podcast, and YouTube subscriptions. New items become sources in the Inbox.

GETList feeds

/api/v1/voice/feeds

RSS, podcast, and YouTube subscriptions. X lists are attached from an engine and are not created here.

Read A signed-in session, or any agent key.

Query parameters

profileIduuidOptional

Limit to one profile.

200. { feeds: VoiceFeedDTO[] }.

POSTSubscribe to a feed

/api/v1/voice/feeds

Poll an RSS feed, a podcast, or a YouTube channel. New items become sources. A person reviews the brief before anything merges.

Write A signed-in session, or an agent key with the write scope.

Body

profileIduuidRequired

Profile that owns the subscription.

kind"rss" | "podcast" | "youtube"Required

x_list is rejected. Attach lists from the engine.

urlstringRequired

http(s) URL. A missing scheme is treated as https. YouTube must be a channel URL.

labelstringOptional

Up to 120 characters.

intervalMinutesintegerOptional

From 60 minutes to 7 days. Default is 360.

201. VoiceFeedDTO.

  • At most 12 feeds per profile. Each poll creates at most 10 sources.

GETGet a feed

/api/v1/voice/feeds/{id}

One subscription, including the last error.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Feed id.

200. VoiceFeedDTO.

PATCHUpdate a feed

/api/v1/voice/feeds/{id}

Pause a feed, change how often it is checked, or rename it.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Feed id.

Body

enabledbooleanOptional

Whether the worker polls it.

intervalMinutesintegerOptional

60 to 10,080.

labelstringOptional

Display label.

200. VoiceFeedDTO.

DELETEUnsubscribe

/api/v1/voice/feeds/{id}

Remove the subscription. Sources the feed already created stay in the library.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Feed id.

200. { ok: true }.

POSTPoll a feed now

/api/v1/voice/feeds/{id}/poll

Check the feed immediately. New items become sources and show in the Inbox as new from the feed.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Feed id.

200. { feedId, created, skipped, error }.

X lists

Bring-your-own X credentials, a read estimate, then circle, radar, and circle voice.

GETRead X credential status

/api/v1/voice/x-credentials

Whether a bearer token, OAuth client, and user list-reader token are stored, plus today’s read count. The secrets themselves are never returned.

Read A signed-in session, or any agent key.

200. XCredentialStatus.

PUTSave X credentials

/api/v1/voice/x-credentials

Store the workspace’s own X app credentials. Tokens are encrypted at rest. Omit a secret field to leave the stored value in place. Send an empty string to clear one.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Body

bearerstringOptional

App bearer token, up to 2,000 characters. Required on the first save.

clientIdstringOptional

OAuth client id for private lists, up to 200 characters.

clientSecretstringOptional

OAuth client secret, up to 2,000 characters.

readerTokenstringOptional

User token that can list the account’s own lists.

dailyReadCapintegerOptional

From 10 to 100,000. Default 2,000.

200. XCredentialStatus after the save.

DELETERemove X credentials

/api/v1/voice/x-credentials

Delete the keys and pause every X list sync. The next sync reports that credentials were removed.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

200. XCredentialStatus with the secrets cleared.

POSTTest the X connection

/api/v1/voice/x-credentials/test

One lookup with the stored bearer token. A success stamps verifiedAt. A failure stores lastError and returns the X error code.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

200. XCredentialStatus.

  • 400 not_configured when no bearer token is stored.
  • 401 or 403 from X becomes invalid_credentials. 429 becomes rate_limited. 402 becomes out_of_credit. The X response body is never copied into the error.

GETList owned X lists

/api/v1/voice/x/lists

Lists the connected X account owns, when a user reader token is stored. Public lists are attached by URL without this call.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

200. { lists: [{ id, name, private }], note }. note explains a missing token or an X error. The HTTP status stays 200 in those cases.

POSTEstimate X list reads

/api/v1/voice/x-lists/estimate

A cap-based estimate. This does not call X. Show it before the first attach and require the caller to confirm it.

Read A signed-in session, or any agent key.

Body

urlstringOptional

https://x.com/i/lists/123 or a bare numeric id. listId is accepted too.

circlebooleanOptional

Sync members into the lens network. Default true.

radarbooleanOptional

Cache recent posts for a digest. Default false. Costs 100 post reads.

circleVoicebooleanOptional

Also build a group persona from the members. Default false.

maxMembersintegerOptional

Member cap from 1 to 5,000. Default 500. Circle and circle voice each count this many reads.

200. { lookupReads, memberReads, postReads, total }.

  • Radar posts are kept for 7 days. A compliance job deletes stored posts that disappear from X.

GETList attached X lists

/api/v1/voice/x-lists

Lists attached to one engine, including the members currently stored.

Read A signed-in session, or any agent key.

Query parameters

engineIduuidRequired

Engine whose lists to return.

200. { lists: XListFeedDTO[] }.

POSTAttach an X list

/api/v1/voice/x-lists

Follow a public or private list after the caller confirms the read estimate. The engine needs a lens. The workspace needs a bearer token.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Body

engineIduuidRequired

Engine that owns the list.

urlstringRequired

List URL or numeric id.

circlebooleanOptional

Write members into the lens network. Default true.

radarbooleanOptional

Keep a short-lived post cache for digests.

circleVoicebooleanOptional

Create a group persona from the circle.

maxMembersintegerOptional

1 to 5,000. Default 500.

acknowledgetrueRequired

Must be true. Confirms the caller saw the estimate.

201. XListFeedDTO. The first sync runs on the worker interval (24 hours) or via POST /sync.

  • 409 conflict when this engine already follows that list.
  • At least one of circle, radar, or circleVoice must be on.

POSTSync an X list now

/api/v1/voice/x-lists/{id}/sync

Run one sync: members into the lens network, a radar digest when enabled, and the circle voice when enabled. Reads count against the daily cap.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Attached list id, not the X list id.

200. { feedId, members, posts, digest, reads, error }. error is empty on success.

  • When the daily cap is reached, sync pauses until the next day.
  • Revoked keys return an error that credentials were removed and sync is paused.

Library and collection

Explorer, search, collections, the interview, and archive imports.

GETLoad the Command Center

/api/v1/voice/explorer

Profiles, engines, source counts, collections, tags, phrase-rule counts, fact counts, and the Inbox.

Read A signed-in session, or any agent key.

200. VoiceExplorerDTO.

  • Inbox kinds: source_processing, source_needs_you, brief_review, unmapped_chunks, phrase_rule_proposed, import_ready, import_failed, fact_proposed, fact_expired, feed_item.

GETList collections

/api/v1/voice/collections

Named groups of sources. At most 100 per workspace.

Read A signed-in session, or any agent key.

200. { collections: [{ id, name, sourceCount, createdAt, updatedAt }] }.

POSTCreate a collection

/api/v1/voice/collections

Name a source collection. Names are at most 60 characters and unique in the workspace.

Write A signed-in session, or an agent key with the write scope.

Body

namestringRequired

Collection name.

201. The collection.

PATCHRename a collection

/api/v1/voice/collections/{id}

Change the collection name. Sources stay put.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Collection id.

Body

namestringRequired

New name.

200. The collection.

DELETEDelete a collection

/api/v1/voice/collections/{id}

Remove the collection. Its sources stay in the library, ungrouped.

Write A signed-in session, or an agent key with the write scope.

Path parameters

iduuidRequired

Collection id.

200. { ok: true }.

POSTApply the voice interview

/api/v1/voice/interview

Write guided-interview answers onto a voice’s working copy. The lens part can create an engine, which only a person may do.

Write A signed-in session, or an agent key with the write scope.

Body

profileIduuidRequired

Personal voice or lens to fill.

answersRecord<string, string>Optional

Question id to answer. Each answer is at most 2,000 characters.

skipstring[]Optional

Section ids, or identity, the person unchecked in the preview.

lens{ engineId?, createEngine? }Optional

Where the lens answers go: an engine you own, or a new engine named after the topic.

200. { profile, sections, identityFields, engine? }.

GETList archive imports

/api/v1/voice/imports

X archive and LinkedIn export imports this caller started.

Read A signed-in session, or any agent key.

200. { imports } with kind unknown, x_archive, or linkedin_export, and status queued, running, ready, applied, or failed.

POSTStart an archive import

/api/v1/voice/imports

Import an uploaded X archive or LinkedIn export. Create the media asset with purpose voice_archive first. A person reviews the report before anything reaches a profile.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Body

profileIduuidRequired

Personal voice or lens the import is for.

mediaAssetIduuidRequired

The uploaded archive.

201. The import row. The worker parses it as voice_archive_import.

  • The report can hold up to 40 post samples, 20 liked posts, and 2,000 network rows.

GETGet an archive import

/api/v1/voice/imports/{id}

The report: identity candidates, sample posts, liked posts, and network rows, once status is ready.

Read A signed-in session, or any agent key.

Path parameters

iduuidRequired

Import id.

200. The import and its report.

DELETEDiscard an import

/api/v1/voice/imports/{id}

Delete the import and its report. Profiles keep whatever was already applied.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Import id.

200. { ok: true }.

POSTApply an archive import

/api/v1/voice/imports/{id}/apply

Write the chosen identity fields, samples, likes, and network rows onto the working copy. With asSource, the chosen posts also become a pasted source and a stance brief is queued.

Human A signed-in human session. An agent key receives 403 privileged_human_required.

Path parameters

iduuidRequired

Import id.

Body

identityFieldsstring[]Optional

Identity card fields to copy. Unknown names are rejected.

sampleIdsstring[]Optional

Post sample ids from the report, shaped like x:abc.

likedIdsstring[]Optional

Liked-post ids.

networkIdsstring[]Optional

Network row ids.

networkSummarybooleanOptional

Fold the circle summary into the identity card.

targetProfileIduuid | nullOptional

Apply to this profile instead of the import’s profile.

asSourcebooleanOptional

Also create a pasted source and queue a brief.

200. What was applied, including sourceId when a source was created.