> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authors, the SERP Brief, Positioning and Titles

> How a content item gets a named author and the Article, BreadcrumbList and FAQPage markup a publish emits, why a draft waits for the SERP brief and what the brief holds, where the brand positioning lives and how the Thesis and Hook lines read from it, and how five claim-shaped titles are generated, picked and tested every week against search, social and email

Until 2026-09-12 a post published under nobody, carried no structured markup beyond the page nodes the SEO suite wrote, was drafted from an outline the writer guessed at rather than the one the search results reward, argued nothing a competitor would not also say, and shipped under the first title the model produced. Four contracts close those gaps. Each is a row, a route and a rule the [Content department](/marketing/content-department), the API and the Claude Code plugin share.

## A named author on every post

### Authors

An account keeps its content authors, the named practitioners its posts publish under, as rows of their own: `name`, `role`, `bio`, `headshot_url`, `credentials`, `same_as` (profile URLs), `voice_notes` and `is_default`. An account with any authors has exactly one default: the first author created becomes it, and `is_default: true` on another author makes that one the default and clears the previous one in the same transaction. Turning `is_default` off on the current default is refused (`409 default_required`); make another author the default instead. Deleting the default while other authors exist is refused too (`409 default_in_use`); the last author can go. A name is unique per account (`409 duplicate_name`). Deleting an author unlinks its items rather than deleting them.

A content item names its author through `author_id`, one of the [eleven fields](/api-reference/marketing-content#create-content) every content writer accepts; every read returns `author { id, name }` beside it. An item with no `author_id` publishes under the account default. `GET /api/olympus/marketing/content-authors` (`content_authors_list`), the create, get, update and delete routes and their dashboard twins are on the [Content API](/api-reference/marketing-content#content-authors).

The [pre-publish check](/marketing/content-seo-check#what-it-checks) raises `author_missing` as an error when the row has no author and the account has no default author, so a post cannot reach the site anonymous by accident. The department reads the authors first, writes `By: <name>` in its header, and when the account has none it says so and offers to create one (`content_authors_create`) before drafting; it never invents a byline.

### The markup a publish emits

`content_publish_to_site` (`POST /api/olympus/marketing/content/{id}/publish-to-site`) does two things with the author on every publish. It maps the resolved author (the item's, else the default) onto the collection's author reference field and creates the entry in the scaffold's authors collection when it is missing, so a preset blog whose schema requires an author no longer fails on that field; a slug already typed into `settings.cms_fields` for the field wins, and a required author field with nothing to map is `422 { field, code: "author_missing" }`, and a required author field whose author exists but whose entry in the authors collection could not be written (the collection missing from the manifest, a field the profile cannot fill, a provider refusal) is `422 { field, code: "author_entry_failed", author_id, warnings[] }` with the cause in the message, to relay rather than to answer with another author. It then builds the page's JSON-LD graph: the WebSite, Organization and WebPage nodes the SEO suite already wrote, followed by an **Article** with the author as a `Person` (name, jobTitle, sameAs, image), `datePublished`, `dateModified`, the hero as its image and the tags as keywords; a **BreadcrumbList** from the page's path; and a **FAQPage** when the item's `faq` holds at least one real question and answer. The graph is stored on the item as `settings.json_ld` (with `json_ld_generated_at`) and written into the collection's JSON-LD field when the collection has one (a string, html or object field named `jsonLd`, `schemaMarkup`, `structuredData` or `customCode`); the publish response says which with `json_ld_target: "field" | "row"`; a value already typed into `settings.cms_fields` for that field is published unchanged and the graph stays on the row. On a static site the deploy injector adds the same three nodes to the page whose route matches the item, so the markup is on the page even when the collection has no field for it.

The `faq` field is `[{ question, answer }]`, at most 20 pairs, and the department fills it with the three to six questions the research and the proof pack surfaced (the SERP brief's people-also-ask, ticket first messages, call summaries), each answered in one to three sentences. `answer_block` is the 40 to 60 word direct answer the department places under the H1 and stores on the row, so an answer engine finds the answer before the argument. The check raises `faq_schema_mismatch` when the body has a FAQ section and `faq` is empty, or the reverse, and `answer_block_missing` when neither the row nor the lead paragraph carries the answer.

An unpublish builds no graph. A Webflow collection reaches the graph only through a mapped field; when it has none the publish returns a `publish_warnings[]` line saying so, and every `publish_warnings[]` line is to be relayed the way the SEO `warnings[]` are.

### llms.txt

Every native publish and unpublish regenerates the project's `llms.txt` (at `public/llms.txt` on a Next.js, React, Vite, Remix, Astro or SvelteKit project, else at the root): the site's pages first, then its published posts by their published route, on the production host, deduped and capped. The publish response carries `llms_txt_regenerated`. The scheduled-publish cron flips an entry live without this path, so a scheduled go-live regenerates nothing until the next manual publish; that gap is known and listed below.

## The SERP brief: the specification a draft waits for

### What a brief holds

The search results for the target keyword are the specification of the page that will rank for it. `POST /api/olympus/marketing/content/{id}/brief` (`content_brief_build`) reads them once and writes the brief:

| Key                                                       | What it holds                                                                                                                                                                                                                                                                 |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `intent`                                                  | `informational`, `commercial`, `transactional` or `navigational`, decided by a rule table over the keyword's modifiers, the SERP features, the top titles and the domains (`intent_signals` lists the rules that fired; `intent_decided_by` is `rules`, `model` or `default`) |
| `page_type`                                               | `guide`, `listicle`, `comparison`, `landing`, `tool`, `news` or `how_to`: the result type the SERP rewards, from the top-ten titles with the top three weighted double                                                                                                        |
| `top_10`                                                  | Position, URL, title, domain and description of the organic results                                                                                                                                                                                                           |
| `top_results`                                             | The top three pages (up to five with `parse_top`) parsed on-page: title, word count, H1 and the H2 outline; a page that fails to parse carries `parse_error` and the brief still returns                                                                                      |
| `word_count_band`                                         | Minimum, maximum, median and a target rounded to the nearest hundred, from the parsed pages                                                                                                                                                                                   |
| `what_the_top_3_all_say`                                  | The H2 themes the parsed pages share, boilerplate dropped, plus a few consensus statements; the thing the piece must cover, and the thing its thesis may disagree with                                                                                                        |
| `paa`, `related_searches`, `features`, `featured_snippet` | The people-also-ask questions (the FAQ candidates), the related searches, the SERP features present and the snippet holder                                                                                                                                                    |

The model is asked only to break a tie: when the intent rules cannot separate two verdicts, or a verdict fell to its default, one JSON completion decides, and an answer outside the vocabularies leaves the rule verdict standing with a warning. Everything else is deterministic.

The brief is stored three ways: as a `serp_brief` knowledge artifact in the account's **Content research** knowledge base (a re-run refreshes the same artifact; find it with `kb_artifacts_list({ artifact_type: "serp_brief" })`, see [Artifacts, and how to find them](/marketing/content-program#artifacts-and-how-to-find-them)); as a row in `seo_serp_analysis` under the SEO project whose domain matches the site, so the stored SERP read finally has a writer; and, last, on the item itself: `search_intent`, `page_type`, `serp_brief` and `serp_brief_captured_at` as columns, plus the `settings.serp_brief` stamp the [research run](/marketing/content-program#the-research-run) reuses for 30 days instead of paying for the SERP again. `POST /api/olympus/marketing/content/brief` (`content_brief_topic`) builds the same brief for a topic that has no item yet and stamps nothing; the artifact is the deliverable.

### Why a draft waits for it

The four brief columns are read-only on `content_create` and `content_update`: sending a value is `400 read_only_field` naming the brief route as the writer, while echoing back exactly what the row holds is accepted, so an autosave or a whole-row PATCH never trips it. The brief route is the only writer, which is what makes the columns trustworthy: when `search_intent` says `commercial`, the SERP said so.

The department builds the brief before the outline. Its header carries `Intent:` and `Type:` read from the row, the outline covers `what_the_top_3_all_say` and lands inside the `word_count_band`, the FAQ comes from `paa`, and the thesis (below) is the sentence that disagrees with the consensus. A draft written before the brief is a draft written to a guess, and the department says so rather than proceeding. `GET` on the same route returns what is stored, so a second writer never rebuilds a brief that exists.

### What a build spends

One live Google SERP read through DataForSEO plus the on-page parses (three by default, five at most), all counted against the account's monthly SEO research cap; a cap already spent is `402 seo_research_limit` with nothing written, and a cap hit part-way through the parses is a warning on the brief. At most one model completion, for the tie-break. The market is the account's SEO location unless `location_name` or a known `location_code` is sent. `Idempotency-Key` is honoured, so a retried POST does not read the SERP twice.

## Positioning: the thesis every piece argues

### The positioning object

The brand positioning lives on the active brand guide (the project's own guide when the piece has a project, else the account-level guide), as one object: `thesis` (the one sentence the brand argues), `beliefs[]`, `we_are_against[]`, `category_name` (the category the brand names, also the category the [bottom-funnel plan](/marketing/content-architecture#the-plan) builds its "best \[category] for \[segment]" pages on) and `proof_points[{ claim, source }]`. `GET` and `PUT /api/olympus/marketing/brand/positioning` (`brand_positioning_get`, `brand_positioning_set`) read and write it; the dashboard twins take `marketing.branding`.

`PUT` replaces the whole object. Send every key each time; a key left out is cleared. Strings are trimmed and capped (thesis 500 characters, category 120, each belief or against 300, twelve entries per list, twelve proof points), a proof point needs both `claim` and `source`, and a `400` names the field. A saved positioning is mirrored as a verified `positioning` knowledge artifact in the Content research knowledge base so `kb_search` finds it; a failed mirror is a warning on the save, never a failed save. The department drafts the object from the mission statement, `brand_is` and `brand_is_not`, the avatars' objections and the proof in the before/after grids, and asks the owner to approve before it writes.

### Thesis and hook in the header

Every draft header now carries two more lines. `Thesis:` is the one sentence the piece argues, from `positioning.thesis` or one of the beliefs, else the piece's own claim; a draft without one is not a draft. `Hook:` is the pattern of the opener, one of the sixteen hook slugs the [social playbook](/marketing/standing-playbooks) and the title generator share, persisted on the item as `settings.hook_pattern`. The rubric gains a fifth axis: at least one sentence a competitor would not publish, which passes when the body carries a belief from the positioning or states a disagreement with what the top three results all say.

## Titles that make a claim

### Five candidates per piece

`POST /api/olympus/marketing/content/{id}/titles` (`content_titles_generate`) asks the content department for five title candidates (up to ten), each tagged with one of the sixteen hook patterns: `specific-number`, `contrarian`, `mistake`, `before-after`, `unanswerable-question`, `persona-callout`, `curiosity-gap`, `objection-first`, `in-medias-res`, `hot-take`, `list-promise`, `myth-truth`, `customer-quote`, `timely`, `proof-teaser`, `definition-reframe`. The patterns offered are filtered to the item's journey stage: a Schwartz stage name such as *Problem Aware* maps exactly, a funnel name goes through the stage classifier (awareness opens unaware and problem-aware patterns, consideration the solution- and product-aware ones, decision and retention the product- and most-aware ones), `curiosity-gap` is open at every stage, and an unknown stage opens all sixteen. The candidates are argued from the positioning when one is stated (`positioning_used`).

The builder validates what the model returns. A line is dropped and named in `dropped[]` when its pattern is not open at the stage, it carries an exclamation mark, it duplicates another candidate or it equals the current title. The survivors are stored as `settings.title_candidates` (replacing the previous set; `hook_pattern` and `title_picked` survive), and nothing else on the item changes. The [pre-publish check](/marketing/content-seo-check#what-it-checks) meanwhile warns `title_generic` on a title with no number, no name, no claim and no contrast, which is the shape every candidate is generated to avoid.

### Picking one

`POST /api/olympus/marketing/content/{id}/titles/pick` (`content_titles_pick`) with a 0-based `index` writes the candidate to `title` and `meta_title` (`apply_to` narrows it to one), sets `settings.hook_pattern` to the candidate's pattern when the H1 changed, stamps `settings.title_picked { index, picked_at, picked_by, applied_to }` so the test below measures from this moment, and records a `content_versions` row through the same writer the update route uses, so the change is in the version list and reversible. The new title reaches the live page only on the next publish of a published piece; nothing republishes on its own, and Search Console keeps measuring the old title until then, so a pick on a live piece is followed by `content_publish_to_site`. `GET` on the titles route returns the candidates, `current_index` (which one is live), the last pick and the latest results. The department presents the five with pattern and rationale and picks the one the user chooses, defaulting to the one whose pattern it used for the opener; it never writes `title_candidates` by hand.

### The weekly title test

Every Monday at 06:10 UTC the `content-title-results` job pools three sources for every published item that carries candidates:

| Source         | Impressions and clicks                                                                                        | How a candidate is tagged                                                    |
| -------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Search Console | The account's permanent query archive, for the live title (the baseline); read-only, never a live Google call | The live `meta_title`, else `title`, matched to a candidate                  |
| Social         | The latest analytics snapshot per published post that carries `linked_content_id`                             | The tag `title:<index>` on the post, or `utm_term=title-<index>` on its link |
| Email          | The sending campaign's delivered count and the click events on the link                                       | `utm_content=<slug>` (the digest convention) plus `utm_term=title-<index>`   |

The job writes `settings.title_results` (`by_candidate[]` with impressions, clicks, CTR and source, the `baseline`, the `winner` with its lift, and `notes[]`), and writes the winning candidate to `meta_title` only, with a `content_versions` row, when it beats the live title by at least 20 percent relative and half a point absolute on at least 500 impressions and 10 clicks over the last 90 days. The H1 stays; a person changes it through the pick, and the winning meta title, like a picked one, is on the page from the next publish. A candidate can win on social and email numbers alone against a Search Console baseline; the thresholds make that deliberate, and `winner.sources` names where the clicks came from. An account with no Search Console archive is listed in `gsc_skipped` with the note *No Search Console archive for this account*, and nothing is written without a baseline.

The off-site convention is what makes the test possible: when the social or email department repurposes a piece that has candidates, each derivative carries a different candidate as its first line and is tagged `title:<index>` (social) or `utm_term=title-<index>` (email). An untagged post is ignored, not counted against any candidate.

## Known limits

* The scheduled-publish cron flips an entry live without the publish path, so a scheduled go-live regenerates no `llms.txt`, stamps no `refreshed_at` and emits no JSON-LD until the next manual publish of that item.
* Server-rendered Next.js posts are not HTML files at deploy time, so the deploy injector cannot stamp them; their graph is on the row (`settings.json_ld`) and in the collection's JSON-LD field when one exists, and the template must render it in the head.
* Search Console CTR lags the sync by two to three days and needs the SEO connection to have written rows; the title job never calls Google itself.
* A winner needs a baseline: an item whose live title is not one of the candidates and has no Search Console rows never gets a winner written, and `notes[]` says so.
* Two guides in one account get two positioning artifacts; the mirror refreshes the artifact of the guide it saved.

## Related pages

* [The Content Department](/marketing/content-department) — the By, Intent, Type, Thesis and Hook lines, and the brief-first rule
* [Site Architecture, Bottom-Funnel Pages, the Decision Loop and Conversion](/marketing/content-architecture) — the other four round-B contracts
* [Distribution, Performance, Proof and Research](/marketing/content-program) — the Content research knowledge base the brief and the positioning are mirrored into
* [Pre-Publish SEO Check](/marketing/content-seo-check) — `author_missing`, `answer_block_missing`, `faq_schema_mismatch` and `title_generic`
* [Brand Guide](/how-tos/brand-guide) — the guide the positioning is stored on
* [Content API](/api-reference/marketing-content#content-authors) — every route on this page with its params and response keys
