Documentation menu

reference

Pipeline

The AI generation engine. The only billable surface in this API.

GET/pipeline/runs

List pipeline runs#

Scope
pipeline:read
Rate limit
600/min (read)
Publishable key
refused
Dashboard permission
pipeline.run

Recent engine activity for the Site, newest first. One row per stage invocation, so a single logical run appears as several rows as work moves through the stages.

Parameters

NameInTypeNotes
cursorquerystring

The opaque cursor from data.next_cursor on the previous page. Do not parse it or construct one; its encoding is not part of this contract and will change.

limitqueryinteger

Page size. Values above the maximum are clamped rather than rejected, so a client asking for a thousand rows gets a hundred and a next_cursor.

default 20

statusqueryPipelineRunStatus

one of running, success, failed, partial

stagequeryPipelineStage

one of dispatch, discover, scrape, extract, plan, generate, improve, classify, images, publish

Responses

  • 200a page of PipelineRun

    A page of pipeline runs.

  • 400

    INVALID_REQUEST. The request could not be parsed, or a parameter is not usable: bad JSON, an unknown query parameter value, or a missing required header.

  • 401

    INVALID_API_KEY, API_KEY_REVOKED or API_KEY_EXPIRED. There is no key, or it is not usable. This response never distinguishes "no such key" from "wrong key", so a caller cannot probe for valid keys.

  • 403

    INSUFFICIENT_SCOPE. The key is valid but does not carry the scope this operation requires, or its creator's permissions no longer cover it.

    This is the only 403 in the API. In particular it is never returned for an object belonging to another Site: that case is always 404, so this response never reveals that something exists.

  • 429

    RATE_LIMIT_EXCEEDED. Back off and honour Retry-After.

    Limits are per key, per minute, by endpoint class. Writes are limited harder than reads because they cost more to serve, and pipeline runs hardest of all because they cost real money.

    ClassEndpointsLimit
    readevery GET600 per minute
    writePOST, PATCH and DELETE on content, taxonomy, keys and webhooks120 per minute
    uploadPOST /media/upload-url60 per minute
    pipelinePOST /pipeline/runs10 per minute

    Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so you can slow down before you are refused rather than after.

    • RateLimit-Limit Requests permitted in the current window for this endpoint class.
    • RateLimit-Remaining Requests left in the current window.
    • RateLimit-Reset Unix seconds at which the window resets.
    • Retry-After Seconds to wait before retrying.
  • 500

    INTERNAL_ERROR. Something failed on our side. The message is deliberately generic; the detail is in our logs against the request_id in the body, so quote it if you contact support.

    Safe to retry, and safer still with the same Idempotency-Key, which guarantees you do not create a second object if the first request actually succeeded.

  • 503

    MAINTENANCE. Writes are paused, either platform wide or for your Site. Reads usually keep working, and your published blog is served from cache and stays up. Retry after the window given in Retry-After.

    • Retry-After Seconds to wait before retrying.

Request shape

curl
curl https://api.writavo.com/v1/pipeline/runs \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"

Generated from the specification, so it shows the path, the required headers and the body shape. The guides carry the end to end examples.

POST/pipeline/runs

Request a pipeline run#

Scope
pipeline:run
Rate limit
10/min (pipeline)
Publishable key
refused
Plan feature
ai.article_generation
Billing
spends credits
Dashboard permission
pipeline.run

This is the only billable operation in this API.

It asks the engine to work on your Site at the next opportunity, rather than waiting for the normal cadence. It returns 202 immediately. It does not wait for articles to be written, and it does not tell you how many will be: what the engine does depends on what is in your queue and how far the credits go.

Four gates, checked in this order, each with its own error code:

GateFailureMeaning
Scope403 INSUFFICIENT_SCOPEThis key does not carry pipeline:run.
Entitlement402 NOT_ENTITLEDYour plan does not include AI generation. Upgrade.
Credits402 INSUFFICIENT_CREDITSThe organisation cannot afford the next unit of work. Top up.
Spend cap402 SPEND_CAP_REACHEDThis Site hit its own monthly ceiling. Raise it or wait for the reset.

The order matters: an entitlement failure is answered before a credits failure, so a caller who is not on the right plan never learns anything about the balance.

Charging happens per unit of work, after that work succeeds, not up front. A run that exhausts the balance part way through stops at the next stage boundary and finishes as partial. Nothing is rolled back and no article is left half written. Poll GET /pipeline/runs/{id} for the outcome, or subscribe to pipeline.run.completed once webhooks are available.

Idempotency-Key is required. Two identical requests within the retention window produce one run, which is what stops a retried network timeout from spending twice.

Parameters

NameInTypeNotes
Idempotency-Key*headerstring

A unique key you generate per logical operation, 1 to 255 printable ASCII characters. A UUID is the obvious choice.

Retry with the same key and the same body and you get the original response replayed rather than a second object. This is what makes a network timeout safe: you never know whether the first request landed, so you retry with the same key and find out.

Same key with a different body is 409 IDEMPOTENCY_KEY_CONFLICT, because reusing a key for different work is a bug in your client rather than a retry. Same key while the first request is still running is 409 IDEMPOTENCY_KEY_IN_FLIGHT; wait and retry.

Keys are scoped to the Site and the endpoint, and are retained for 24 hours. After that the same key is a new operation.

Request body (optional)

FieldTypeNotes
max_articlesinteger

An upper bound on how many articles this run may produce. Your own safety valve on top of the platform spend cap. Omit to use the Site's configured batch size.

Responses

  • 202

    The run was accepted and queued.

    FieldTypeNotes
    id*uuid
    status*"queued"
    requested_at*date-time
    estimated_start_atdate-time | null

    When the engine expects to pick this up. Advisory.

  • 400

    INVALID_REQUEST. The request could not be parsed, or a parameter is not usable: bad JSON, an unknown query parameter value, or a missing required header.

  • 401

    INVALID_API_KEY, API_KEY_REVOKED or API_KEY_EXPIRED. There is no key, or it is not usable. This response never distinguishes "no such key" from "wrong key", so a caller cannot probe for valid keys.

  • 402

    NOT_ENTITLED, INSUFFICIENT_CREDITS or SPEND_CAP_REACHED. The request was understood and you were permitted to make it, but it cannot be paid for. Three distinct codes because the fixes differ: upgrade the plan, top up credits, or raise the Site's cap.

    These are 402 rather than 403 on purpose. A 403 says "you may not"; these all say "not yet, and here is how to change that".

  • 403

    INSUFFICIENT_SCOPE. The key is valid but does not carry the scope this operation requires, or its creator's permissions no longer cover it.

    This is the only 403 in the API. In particular it is never returned for an object belonging to another Site: that case is always 404, so this response never reveals that something exists.

  • 409

    SLUG_CONFLICT, IDEMPOTENCY_KEY_CONFLICT, IDEMPOTENCY_KEY_IN_FLIGHT or CONFLICT. The request is valid but collides with the current state: a slug is taken, an idempotency key was reused with a different body or is still in flight, or the object moved while you were working on it. Read code to tell which, then re-read and retry.

  • 422

    VALIDATION_FAILED. The request parsed but the values are not acceptable. error.fields maps each offending field to a message you can put next to the input.

    Common causes: publishing without a title, slug or content; scheduling in the past; and sending status on a create or update, which is how the API refuses to let a client push content into the AI pipeline.

  • 429

    RATE_LIMIT_EXCEEDED. Back off and honour Retry-After.

    Limits are per key, per minute, by endpoint class. Writes are limited harder than reads because they cost more to serve, and pipeline runs hardest of all because they cost real money.

    ClassEndpointsLimit
    readevery GET600 per minute
    writePOST, PATCH and DELETE on content, taxonomy, keys and webhooks120 per minute
    uploadPOST /media/upload-url60 per minute
    pipelinePOST /pipeline/runs10 per minute

    Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so you can slow down before you are refused rather than after.

    • RateLimit-Limit Requests permitted in the current window for this endpoint class.
    • RateLimit-Remaining Requests left in the current window.
    • RateLimit-Reset Unix seconds at which the window resets.
    • Retry-After Seconds to wait before retrying.
  • 500

    INTERNAL_ERROR. Something failed on our side. The message is deliberately generic; the detail is in our logs against the request_id in the body, so quote it if you contact support.

    Safe to retry, and safer still with the same Idempotency-Key, which guarantees you do not create a second object if the first request actually succeeded.

  • 503

    MAINTENANCE. Writes are paused, either platform wide or for your Site. Reads usually keep working, and your published blog is served from cache and stays up. Retry after the window given in Retry-After.

    • Retry-After Seconds to wait before retrying.

Request shape

curl
curl -X POST https://api.writavo.com/v1/pipeline/runs \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'

Generated from the specification, so it shows the path, the required headers and the body shape. The guides carry the end to end examples.

GET/pipeline/runs/{id}

Read one pipeline run#

Scope
pipeline:read
Rate limit
600/min (read)
Publishable key
refused
Another Site's id
404
Dashboard permission
pipeline.run

The outcome of a run. status: partial with an error_summary is what you see when a run stopped early, whether because credits ran out, the spend cap was reached, or a vendor call failed. items_succeeded tells you what you did get.

Parameters

NameInTypeNotes
id*pathuuid

Responses

  • The pipeline run.

  • 401

    INVALID_API_KEY, API_KEY_REVOKED or API_KEY_EXPIRED. There is no key, or it is not usable. This response never distinguishes "no such key" from "wrong key", so a caller cannot probe for valid keys.

  • 403

    INSUFFICIENT_SCOPE. The key is valid but does not carry the scope this operation requires, or its creator's permissions no longer cover it.

    This is the only 403 in the API. In particular it is never returned for an object belonging to another Site: that case is always 404, so this response never reveals that something exists.

  • 404

    NOT_FOUND. Either no such object exists, or it exists and belongs to a different Site.

    These two cases are deliberately indistinguishable, and neither ever returns 403. A 403 would confirm the object exists, which would let anyone with a valid key enumerate other customers' content by id. If you are certain the id is right, check you are using the key for the correct Site.

  • 429

    RATE_LIMIT_EXCEEDED. Back off and honour Retry-After.

    Limits are per key, per minute, by endpoint class. Writes are limited harder than reads because they cost more to serve, and pipeline runs hardest of all because they cost real money.

    ClassEndpointsLimit
    readevery GET600 per minute
    writePOST, PATCH and DELETE on content, taxonomy, keys and webhooks120 per minute
    uploadPOST /media/upload-url60 per minute
    pipelinePOST /pipeline/runs10 per minute

    Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so you can slow down before you are refused rather than after.

    • RateLimit-Limit Requests permitted in the current window for this endpoint class.
    • RateLimit-Remaining Requests left in the current window.
    • RateLimit-Reset Unix seconds at which the window resets.
    • Retry-After Seconds to wait before retrying.
  • 500

    INTERNAL_ERROR. Something failed on our side. The message is deliberately generic; the detail is in our logs against the request_id in the body, so quote it if you contact support.

    Safe to retry, and safer still with the same Idempotency-Key, which guarantees you do not create a second object if the first request actually succeeded.

  • 503

    MAINTENANCE. Writes are paused, either platform wide or for your Site. Reads usually keep working, and your published blog is served from cache and stays up. Retry after the window given in Retry-After.

    • Retry-After Seconds to wait before retrying.

Request shape

curl
curl https://api.writavo.com/v1/pipeline/runs/3f1b0c7a-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"

Generated from the specification, so it shows the path, the required headers and the body shape. The guides carry the end to end examples.

GET/pipeline/queue

Read the content queue#

Scope
pipeline:read
Rate limit
600/min (read)
Publishable key
refused
Dashboard permission
pipeline.run

What the engine plans to write, highest priority first. Each item is a topic or keyword with a source: manual if a person added it, content_gap if gap analysis found it, competitor_seed if it came from a competitor page.

Read only in v1. Adding topics is a dashboard action.

signal carries the structured research behind a competitor_seed item: the angle, the hook, the data points and the gaps. It is extracted signal, never copied prose, which is the firewall that keeps generated output original.

Parameters

NameInTypeNotes
cursorquerystring

The opaque cursor from data.next_cursor on the previous page. Do not parse it or construct one; its encoding is not part of this contract and will change.

limitqueryinteger

Page size. Values above the maximum are clamped rather than rejected, so a client asking for a thousand rows gets a hundred and a next_cursor.

default 20

statusqueryPlanItemStatus

one of planned, in_progress, done, skipped

sourcequeryPlanItemSource

one of content_gap, manual, competitor_seed

Responses

  • 200a page of QueueItem

    A page of queue items.

  • 400

    INVALID_REQUEST. The request could not be parsed, or a parameter is not usable: bad JSON, an unknown query parameter value, or a missing required header.

  • 401

    INVALID_API_KEY, API_KEY_REVOKED or API_KEY_EXPIRED. There is no key, or it is not usable. This response never distinguishes "no such key" from "wrong key", so a caller cannot probe for valid keys.

  • 403

    INSUFFICIENT_SCOPE. The key is valid but does not carry the scope this operation requires, or its creator's permissions no longer cover it.

    This is the only 403 in the API. In particular it is never returned for an object belonging to another Site: that case is always 404, so this response never reveals that something exists.

  • 429

    RATE_LIMIT_EXCEEDED. Back off and honour Retry-After.

    Limits are per key, per minute, by endpoint class. Writes are limited harder than reads because they cost more to serve, and pipeline runs hardest of all because they cost real money.

    ClassEndpointsLimit
    readevery GET600 per minute
    writePOST, PATCH and DELETE on content, taxonomy, keys and webhooks120 per minute
    uploadPOST /media/upload-url60 per minute
    pipelinePOST /pipeline/runs10 per minute

    Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so you can slow down before you are refused rather than after.

    • RateLimit-Limit Requests permitted in the current window for this endpoint class.
    • RateLimit-Remaining Requests left in the current window.
    • RateLimit-Reset Unix seconds at which the window resets.
    • Retry-After Seconds to wait before retrying.
  • 500

    INTERNAL_ERROR. Something failed on our side. The message is deliberately generic; the detail is in our logs against the request_id in the body, so quote it if you contact support.

    Safe to retry, and safer still with the same Idempotency-Key, which guarantees you do not create a second object if the first request actually succeeded.

  • 503

    MAINTENANCE. Writes are paused, either platform wide or for your Site. Reads usually keep working, and your published blog is served from cache and stays up. Retry after the window given in Retry-After.

    • Retry-After Seconds to wait before retrying.

Request shape

curl
curl https://api.writavo.com/v1/pipeline/queue \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"

Generated from the specification, so it shows the path, the required headers and the body shape. The guides carry the end to end examples.

Schemas

The shapes this resource sends and returns. Unknown fields may be added within /v1, so tolerate them.

PipelineRun#

FieldTypeNotes
id*uuidread only
stage*PipelineStage

The engine stage a run belongs to.

status*PipelineRunStatus

partial means the run did useful work and then stopped early. That is the normal outcome when credits or a spend cap run out mid run, and it is not an error: what was produced is kept.

started_at*date-time
finished_atdate-time | null
items_processedinteger
items_succeededinteger
items_failedinteger
triggered_bystring

cron for the normal cadence, or api for a run you requested.

error_summarystring | null

Why the run ended early, when it did. On a partial run this is where you find out it was credits, the spend cap, or a vendor failure.

PipelineRunStatus#

partial means the run did useful work and then stopped early. That is the normal outcome when credits or a spend cap run out mid run, and it is not an error: what was produced is kept.

runningsuccessfailedpartial

PipelineStage#

The engine stage a run belongs to.

dispatchdiscoverscrapeextractplangenerateimproveclassifyimagespublish

PlanItemSource#

Where the topic came from. manual if a person added it, content_gap if gap analysis found it, competitor_seed if it came from a competitor page.

content_gapmanualcompetitor_seed

PlanItemStatus#

plannedin_progressdoneskipped

QueueItem#

FieldTypeNotes
id*uuidread only
topic_or_keyword*string
source*PlanItemSource

Where the topic came from. manual if a person added it, content_gap if gap analysis found it, competitor_seed if it came from a competitor page.

status*PlanItemStatus
signalobject | null

Structured research for a competitor sourced item: the angle, hook, data points and gaps found. Extracted signal, never copied prose.

target_publish_datedate | null
priorityinteger
linked_article_iduuid | null

The article this item produced, once it has been written.

created_atdate-timeread only