The two fields that only exist in the interface
Two SEO fields have no API path on the platform I syndicate to. That single gap is the reason the pipeline stops at a draft instead of going live.
A publishing API accepts a title, a body, tags, a canonical link, a publication, and a publish state. It does not accept the two fields that control how the post appears in a search result.
Those exist only in the web interface. Everything else about my pipeline follows from that.
The case this post is about
One post, created through an API that covers everything except two fields:
$ create-draft three-phases
draft: https://example.com/p/9f2c1b05
remaining (manual, no API path):
title Composition, layout, drawing — and why reading a value... (57/60)
description A value read in the wrong phase costs you the two after... (147)
The draft is real. The body, the tags, the canonical link, the publication — all of it went through the API correctly. Two fields have no endpoint, so a person has to open the editor and paste them.
That is why drafts exist at all here: not as a review stage, but because the API automates the part before publishing and nothing can automate the rest.
This post follows those two fields, because a tool reporting "published" at this point would be claiming an outcome it cannot produce — and the two fields would stay empty on a live post.
The symptom
An otherwise complete automation stops one step short. The post is created, correctly, with every field the API knows about — and it is created as a draft, because publishing it would publish it without the two fields that cannot be set programmatically.
The second thing is that the gap is not visible in the API's documentation as a gap. The endpoint describes what it accepts; it does not describe what the platform has that the endpoint does not. You discover the omission by looking for a field and failing to find it, which is a search that only happens if you already know the field exists.
The third is how easily it is rationalised away. Those two fields are optional — the platform will derive something from the title and the opening paragraph if they are absent. So the pipeline appears to work completely, and the cost is invisible: every post goes out with a derived summary instead of a written one, and nothing reports it.
The fourth is that this single gap determines the shape of everything upstream. Because the last step is manual, the pipeline has to produce something a human can finish rather than something that is finished — which changes what gets generated, what gets checked, and where the hand-off happens. One missing endpoint reorganises the whole design.

That reorganisation is the part worth planning for rather than absorbing. A pipeline ending in a human step needs different outputs, different reporting and a different idea of what finished means — and discovering the gap late means retrofitting all three onto something built on the assumption that it would run unattended.
Why the obvious fix failed
Use the derived values. What happens by default, and it discards the one surface where a specific claim earns a click. A derived summary is the first sentences of the post, which were written to open an article rather than to make somebody choose it from a list of ten.
Drive the interface with a browser automation. Technically possible and a poor trade. It is brittle against layout changes, it operates with full session credentials against a system with no undo, and it converts a two-minute manual step into a maintenance burden that fails silently when the page changes.
Ask for the API to cover it. Reasonable, slow, and outside my control. Worth doing and not a plan: a pipeline that depends on somebody else's roadmap is a pipeline that does not work yet.
Publish anyway and fix the fields afterwards. Tempting, and it loses the window that matters. The platform fetches and caches on publication; the version seen by early readers and by anything crawling it is the one that existed at that moment, and editing afterwards does not reliably retroactively change what was cached.

The actual mechanism
Design the pipeline to stop at the last fully-automatable step, and make the manual remainder small, explicit and the same every time.
automate everything the API genuinely covers, to a draft
prepare the two manual values, computed and validated, in the post's own file
present them where the human doing the step can copy them without thinking
stop there, and say so plainly, rather than implying the post is live
The third line is where the real work went. The manual step is unavoidable; what is avoidable is the human also having to compose the values under time pressure in a web form. Generating them beforehand, validating them against the length bands, and having them sit beside the post turns a creative task into a transcription.
Transcription has the further advantage of being checkable. A value computed ahead of time can be validated against the platform's length bands before anybody opens a browser, so the manual step cannot be where a too-long summary is discovered — which is exactly where it would otherwise be found, with the form open and the post half-published.
The sub-case that keeps this honest is the reporting. A pipeline that ends in a draft must not describe itself as having published. The distinction between "created" and "live" has to survive into every status output, or the gap closes in the reporting rather than in reality — which is a worse failure than the one it conceals.

The temptation to blur it is strong because the blurred version sounds better. "Published sixty posts" is a sentence somebody wants to write at the end of a run, and it is false in a way nobody will catch until they look for the posts. Reporting the true state costs a slightly duller summary and is the only version that stays true a week later.
There is a related decision about batch size that follows directly from the manual tail. Creating drafts is cheap and reversible-ish; finishing them is a human minute each. A run that creates sixty drafts has produced an hour of someone's afternoon, and the honest unit of work is however many a person will actually finish rather than however many the API will accept.
Getting that number wrong is how a backlog of drafts becomes its own problem. Sixty unfinished drafts on a platform look, to anybody browsing the account, exactly like sixty abandoned posts — and the pipeline that produced them in one confident run has converted a clean inbox into a visible obligation.
The fix
generate the manual values alongside the post, validated, ahead of time
create the draft through the API, one at a time
report "draft created — the remaining step is manual" and name it
size batches to what a human will finish, not to what the API allows
The manual values are generated and validated before the draft is created, so the human step is paste-and-publish rather than compose-under-pressure:
const BAND = { min: 140, max: 156 };
export function manualValues(post: Post) {
const title = post.meta.seo_title ?? post.meta.title;
const description = `${post.meta.description} ${post.meta.subtitle}`.trim().slice(0, BAND.max);
const problems: string[] = [];
if (title.length > 60) problems.push(`title ${title.length}/60 — will be truncated`);
if (description.length < BAND.min) problems.push(`description ${description.length} < ${BAND.min}`);
if (!/[.!?]$/.test(description)) problems.push("description is severed mid-sentence");
// Validated BEFORE the draft exists. Discovering a severed description after
// creation means fixing it in a web form, which is where errors get made.
if (problems.length) throw new Error(`${post.slug}: ${problems.join("; ")}`);
return { title, description };
}
export async function createOne(post: Post) {
const manual = manualValues(post);
const draft = await api.createDraft(post);
// Report the boundary honestly: this is finished work for the tool.
return { draft, remaining: "set these two fields, then publish — no API path exists", manual };
}
What one invocation produces:
$ create-draft three-phases
draft: https://example.com/p/9f2c1b05
remaining (manual, no API path):
title Composition, layout, drawing — and why reading a value... (57/60)
description A value read in the wrong phase costs you the two after... (147)
The honest report is the point. A tool that said "published" here would be describing an outcome it cannot produce, and the two fields would stay empty on a live post.
The second line says one at a time for a reason unrelated to this gap: the platform's API has no delete endpoint, so a batch that creates the wrong thing creates permanent wrong things. The manual tail happens to enforce a pace that also protects against that, which is a convenient accident rather than a design.
What makes the arrangement tolerable is that the manual step is identical every time: open, paste, paste, publish. It is the repetition that makes it fast, and the repetition only exists because the values are prepared. The version where somebody writes the summary in the form takes five times as long and produces worse summaries.
Worse for a specific reason: a summary written in the publishing form is written after the work is finished, by somebody whose remaining goal is to be done. The same sentence written while the post is still open, as part of the editorial pass, competes with the writing rather than with the exit — and that difference shows up in whether it makes a claim or merely describes a topic.
How we proved it
The gap is verifiable in one request. Create a post with the fields the API documents, then open it in the interface and look at the settings panel. The two fields are empty and editable there, and nothing in the API payload could have set them. That takes a minute and settles the question of whether it is a gap or a mistake in my reading.
The derived-summary cost is visible side by side. Put a derived summary next to a written one for the same post. The derived version opens with the article's first sentence, which is almost always scene-setting rather than a claim, and the comparison makes the case without needing a click-through measurement.
What I cannot tell you is what the manual step costs in practice over a year. I know it is about a minute per post and I have not tracked it, so the figure I would quote is reconstruction. The honest version is that it is small per post and the aggregate is the reason batch size matters.
What I have not solved: the posts already published without those fields. They are live, the summaries are derived, and editing them now updates the fields without reliably updating what has already been cached and indexed. The fix applies forward, which is unsatisfying and is the actual state of things.
I considered a backfill and decided against it on the grounds that it would be work with an unknowable return — the fields would be correct afterwards, and whether anything would re-read them is outside my view. That may be the wrong call; it is at least a call rather than an omission.
And a limitation of the whole approach: stopping at the last automatable step is right and it means the pipeline's output is not the thing you wanted. "Sixty drafts ready" is genuinely less valuable than "sixty posts live", and no amount of good design closes that gap — it just stops pretending it is closed.
The thing that reframed it was realising the gap is not an inconvenience to route around but a fact that should shape the design. Once I stopped looking for a way to automate the last step and started designing for a human finishing it, the questions got better: what can I prepare, what can I validate, what can I make identical every time. Those have answers. "How do I set a field with no endpoint" does not.
What this generalizes to
An API is a subset of a product, and the parts outside it are rarely documented as being outside it.
This is the shape of most integration disappointment. A billing system whose refund reasons are a dropdown with no programmatic equivalent. An identity provider whose one setting lives in a console. A deployment platform with a toggle no client exposes. In each case the automation is ninety-five per cent complete and the remaining five per cent determines whether the thing can run unattended — which is a binary property, not a percentage.
That binary is why "mostly automated" misreports this kind of work. A process that needs a person at one point needs a person, and the ninety-five per cent only changes how long they are there for. It is a real improvement and it is not the category change the percentage implies.
So the useful question before designing around any API is what can the interface do that this cannot, asked deliberately rather than discovered. The answer decides whether you are building an automation or a very good preparation step, and those are different things that deserve to be named differently.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".