Skip to content

Article API

Endpoints for managing the "10-second race result summary" articles (article) tied to races.

Articles and races

Each article is tied through race_id to a race (_id) in one of the auto_racing / bicycle_racing / horse_racing tables. Articles have no category (only blog / old_blog do).

Article status

Status Description
generating Generating; AI article generation / build is in progress
draft Draft (default)
published Published
failed Generation failed; the reason is in generation_error. Starting point for regeneration

generating and failed are set by the generation pipeline. They cannot be supplied as input to POST /v1/articles or PUT /v1/articles/{article_id} (doing so returns 400).

Article schema

Field Type Notes
id string _id
race_id string _id of one of the racing tables
title string
body_html string HTML. For AI-generated articles, produced by the article_builder Lambda rendering the RaceSummary React component with renderToStaticMarkup
status enum generating / draft / published / failed. Default: draft
opening_at number Scheduled publish time. UNIX timestamp (ms)
closing_at number | null Scheduled unpublish time. UNIX timestamp (ms)
accuracy_test_data object | null Accuracy-verification data, read by scripts/article_accuracy_test/audit.py
generation_error string | null Failure reason when status=failed
custom_prompt string | null Extra instructions used by the last regeneration; prefilled in the input field
created_at number UNIX timestamp (ms)
updated_at number UNIX timestamp (ms)

POST /v1/articles/generation

Overview

Generates an article with generative AI.

The article is created in generating state (with an empty body_html) and a generation job is dispatched asynchronously to the article_generation Lambda through SQS (the article-generation queue). The request returns 202 immediately and generation runs in the background. The result is handed to the article_builder Lambda over another SQS queue (article-builder), which builds the article HTML (body_html), stores it in DocumentDB and moves status to draft.

To create an article without AI, use POST /v1/articles.

Void / cancelled races cannot be generated

Races with no finishing order (void or cancelled) are rejected. With nothing to base the finishing order on, the AI invents a winner. Such requests return 422.

Request and Response

Headers

Request

  • Content-Type: application/json

Response

  • Content-Type: application/json

URI

POST /v1/articles/generation

Request body

{
  "race_id": "string",
  "title": "string",
  "opening_at": 1234567890000,
  "closing_at": 1234567890000
}
Field Type Required Notes
race_id string โ—ฏ _id of one of the racing tables
title string โ—ฏ Title / topic of the article to generate
opening_at number โ—ฏ UNIX timestamp (ms)
closing_at number UNIX timestamp (ms)

Response

202 Accepted

{
  "id": "string"
}

Error handling

Description Status code Status
Invalid request body 422 Unprocessable Entity
race_id not found in any racing table 400 Bad Request
Race has no finishing order (void / cancelled) 422 Unprocessable Entity
Internal server error 500 Internal Server Error

Processing flow

  1. Receive the request
  2. Verify race_id exists in one of the three race collections
  3. Verify the race result has a finishing order (not void / cancelled)
  4. Create the article in generating state with an empty body_html
  5. Send {article_id, race_id, racing_type, title, race_result} to the article-generation queue
  6. Return 202 Accepted with the new id
  7. (async) article_generation analyses the race, writes the article and sends it to article-builder
  8. (async) article_builder builds body_html, stores it and moves status to draft
flowchart TD
    Start([POST /v1/articles/generation]) --> RaceCheck[Check race_id exists]
    RaceCheck -->|not found| E400[400 Bad Request]
    RaceCheck -->|OK| VoidCheck[Check finishing order exists]
    VoidCheck -->|none| E422[422 Unprocessable Entity]
    VoidCheck -->|OK| SaveArticle[Save article as generating]
    SaveArticle --> SendSQS[Send to article-generation queue]
    SendSQS --> Success[202 Accepted]
    SendSQS -.->|async| Generate[article_generation analyses + writes]
    Generate -.-> Build[article_builder builds HTML and stores it]
    Build -.-> Draft[status becomes draft]
    Generate -.->|failed on final attempt| Failed[status becomes failed]

POST /v1/articles/{article_id}/regeneration

Overview

Rebuilds an article that failed to generate (status=failed), optionally with extra instructions.

Regenerating with the same input fails for the same reason, so an operator can supply writing instructions (custom_prompt). If omitted, the previous instructions are reused, so repeatedly triggering regeneration from the list view does not lose them.

Only status=failed articles are eligible: regenerating a draft or published article would overwrite reviewed content.

Request and Response

Headers

Request

  • Content-Type: application/json

URI

POST /v1/articles/{article_id}/regeneration
Path parameter Type Notes
article_id string article._id

Request body

{
  "custom_prompt": "string"
}
Field Type Required Notes
custom_prompt string Extra instructions from the operator. Omitted or empty reuses the previous instructions

Response

202 Accepted

{
  "id": "string"
}

Error handling

Description Status code Status
Article not found 404 Not Found
Article status is not failed 409 Conflict
race_id not found 400 Bad Request
Race has no finishing order (void / cancelled) 422 Unprocessable Entity
Internal server error 500 Internal Server Error

Processing flow

flowchart TD
    Start([POST /v1/articles/article_id/regeneration]) --> Fetch[Fetch the article]
    Fetch -->|not found| E404[404 Not Found]
    Fetch -->|found| StatusCheck[Check status=failed]
    StatusCheck -->|not failed| E409[409 Conflict]
    StatusCheck -->|OK| VoidCheck[Check the race finishing order]
    VoidCheck -->|none| E422[422 Unprocessable Entity]
    VoidCheck -->|OK| Reset[Reset status to generating, store custom_prompt]
    Reset --> SendSQS[Send to article-generation queue]
    SendSQS --> Success[202 Accepted]

Status is reset before enqueueing

article_generation only processes articles with status=generating, so the status is updated before the message is enqueued.


POST /v1/articles

Overview

Creates an article manually.

Request and Response

Headers

Request

  • Content-Type: application/json

Response

  • Content-Type: application/json

URI

POST /v1/articles

Request body

{
  "race_id": "string",
  "title": "string",
  "body_html": "string",
  "status": "draft",
  "opening_at": 1234567890000,
  "closing_at": 1234567890000
}
Field Type Required Notes
race_id string โ—ฏ _id of one of the racing tables
title string โ—ฏ
body_html string โ—ฏ HTML
status enum draft / published. Defaults to draft. generating / failed are rejected
opening_at number โ—ฏ UNIX timestamp (ms)
closing_at number UNIX timestamp (ms)

Response

201 Created โ€” returns the article schema.

Error handling

Description Status code Status
Invalid request body 422 Unprocessable Entity
status set to generating / failed 400 Bad Request
race_id not found 400 Bad Request
Internal server error 500 Internal Server Error

GET /v1/articles

Overview

Lists articles.

Request and Response

URI

GET /v1/articles

Query parameters

Parameter Type Required Notes
race_id string Filter by race
status enum Filter by generating / draft / published / failed
page number Page number. Default: 1
size number Items per page. Default: 10
order enum asc / desc (by updated_at). Default: desc

Response

200 OK

{
  "list": [
    {
      "id": "string",
      "race_id": "string",
      "title": "string",
      "body_html": "string",
      "status": "draft",
      "opening_at": 1234567890000,
      "closing_at": null,
      "accuracy_test_data": null,
      "generation_error": null,
      "custom_prompt": null,
      "created_at": 1234567890000,
      "updated_at": 1234567890000
    }
  ],
  "current_page": 1,
  "total_count": 10
}

Failed-generation list

The admin "failed generations" screen is backed by GET /v1/articles?status=failed.


GET /v1/articles/{article_id}

Overview

Fetches a single article.

URI

GET /v1/articles/{article_id}

Response

200 OK โ€” returns the article schema.

Error handling

Description Status code Status
Article not found 404 Not Found
Internal server error 500 Internal Server Error

GET /v1/races/{race_id}/articles/{article_id}

Overview

Fetches an article scoped to a race. If the article's race_id does not match the path, 404 is returned.

The former per-sport endpoints (/v1/auto_racings/... and friends) were consolidated here along with the race API (/v1/races).

URI

GET /v1/races/{race_id}/articles/{article_id}
Path parameter Type Notes
race_id string The race _id
article_id string article._id

Response

200 OK โ€” returns the article schema.

Error handling

Description Status code Status
Article not found 404 Not Found
The article's race_id does not match the path 404 Not Found
Internal server error 500 Internal Server Error
flowchart TD
    Start([GET /v1/races/race_id/articles/article_id]) --> Fetch[Look up in DB]
    Fetch -->|not found| E404[404 Not Found]
    Fetch -->|found| RaceCheck[Compare race_id]
    RaceCheck -->|mismatch| E404_race[404 Not Found]
    RaceCheck -->|match| Success[200 OK]

PUT /v1/articles/{article_id}

Overview

Updates an article.

Request body

{
  "race_id": "string",
  "title": "string",
  "body_html": "string",
  "status": "published",
  "opening_at": 1234567890000,
  "closing_at": 1234567890000,
  "accuracy_test_data": {}
}
Field Type Required Notes
race_id string _id of one of the racing tables
title string
body_html string HTML
status enum draft / published. generating / failed are rejected
opening_at number UNIX timestamp (ms)
closing_at number UNIX timestamp (ms)
accuracy_test_data object Accuracy-test data. See the article table for its structure

Fields left out (null) are not updated.

Update body_html and accuracy_test_data together

When the body is corrected mechanically (e.g. "wire-to-wire" โ†’ "held on"), updating only body_html leaves the accuracy_test_data read by the audit (scripts/article_accuracy_test/audit.py) stale, so the rendered article and the verification result disagree. Both can be updated in a single request.

Response

200 OK โ€” returns the updated article schema.

Error handling

Description Status code Status
Invalid request body 422 Unprocessable Entity
status set to generating / failed 400 Bad Request
race_id given but the race does not exist 400 Bad Request
Article not found 404 Not Found
Internal server error 500 Internal Server Error

DELETE /v1/articles/{article_id}

Overview

Soft-deletes an article.

URI

DELETE /v1/articles/{article_id}

Response

204 No Content

Error handling

Description Status code Status
Article not found 404 Not Found
Internal server error 500 Internal Server Error