Race API
Endpoints for managing auto racing, keirin and horse racing race information.
This data feeds AI article generation (article) and AI blog generation (blog), and is referenced
through article.race_id / blog.race_id.
Racing type
Races are stored in three collections by racing type: auto_racing / bicycle_racing /
horse_racing. Race documents themselves carry no type โ the collection expresses the type.
| Value | Sport |
|---|---|
auto_racing |
Auto racing |
bicycle_racing |
Keirin |
horse_racing |
Horse racing |
Because race_id (the _id) is unique across racing types, fetching, updating and deleting a single
race needs no type: the API searches all three collections. Only listing (GET /v1/races) requires
type, since it must decide which collection to read.
Race schema
| Field | Type | Notes |
|---|---|---|
| id | string | _id; the race_id inside the race result JSON |
| type | enum | auto_racing / bicycle_racing / horse_racing |
| name | string | Uploaded race result JSON filename without .json |
| race_result | object | Contents of the race result JSON |
| prediction | object | null | Pre-race entry list and AI prediction marks. null when not registered |
| article_status | enum | null | Status of the latest article for this race; null if none (set only in list responses) |
| created_at | number | UNIX timestamp (ms) |
| updated_at | number | UNIX timestamp (ms) |
POST /v1/races
Overview
Registers a new race result.
No JSON payload is sent in the body; instead the racing type (type) and the race result JSON file
are uploaded as multipart/form-data. The contents of the uploaded JSON file are stored verbatim as
race_result.
Request and Response
Headers
Request
Content-Type: multipart/form-data
Response
Content-Type: application/json
URI
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| type | enum | โฏ | auto_racing / bicycle_racing / horse_racing |
| file | binary | โฏ | Race result JSON file |
The server derives each stored field as follows.
_id: therace_idvalue inside the JSONname: the uploaded filename minus.json(e.g.20260504_้ฃฏๅก_05R.jsonโ20260504_้ฃฏๅก_05R)race_result: the whole JSON body, stored as-is
Example of a race result JSON (the format follows the scraping output):
{
"race_id": "string",
"race_date": "string",
"track_name": "string",
"race_number": "string",
"race_name": "string",
"weather": "string",
"entries": []
}
Response
201 Created
{
"id": "string",
"type": "horse_racing",
"name": "string",
"race_result": {},
"prediction": null,
"article_status": null,
"created_at": 1234567890000,
"updated_at": 1234567890000
}
Error handling
| Description | Status code | Status |
|---|---|---|
| No file supplied | 400 | Bad Request |
| Body cannot be parsed as JSON | 400 | Bad Request |
JSON has no race_id field |
400 | Bad Request |
Invalid type |
422 | Unprocessable Entity |
A race with the same race_id already exists |
409 | Conflict |
| Internal server error | 500 | Internal Server Error |
Processing flow
- Receive the request
- Parse the uploaded JSON file and extract
race_id - Search all three race collections by
race_idto confirm the race does not exist yet - Store the race in the collection matching
type - Return the stored race
flowchart TD
Start([POST /v1/races]) --> Parse[Parse JSON file]
Parse -->|invalid| E400[400 Bad Request]
Parse -->|no race_id| E400
Parse -->|OK| Exists[Search three collections by race_id]
Exists -->|already exists| E409[409 Conflict]
Exists -->|not found| Save[Store into the collection for type]
Save --> Success[201 Created]
PUT /v1/races/prediction
Overview
Attaches an AI prediction (pre-race entry list and prediction marks) to an existing race.
Predictions are sometimes collected after the results, so this is a separate operation from race
registration. The stored prediction is the input for prediction blog generation
(POST /v1/blogs/generate with kind=prediction).
Request and Response
Headers
Request
Content-Type: multipart/form-data
Response
Content-Type: application/json
URI
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| file | binary | โฏ | Prediction JSON file; must contain race_id and entries |
The target race is identified by the race_id inside the JSON, so no path or form parameter is needed.
Response
200 OK
Returns the race schema with prediction populated.
Error handling
| Description | Status code | Status |
|---|---|---|
| No file supplied | 400 | Bad Request |
| Body cannot be parsed as JSON | 400 | Bad Request |
JSON has no race_id field |
400 | Bad Request |
JSON has no entries field |
400 | Bad Request |
| Race not found | 404 | Not Found |
| Internal server error | 500 | Internal Server Error |
Processing flow
flowchart TD
Start([PUT /v1/races/prediction]) --> Parse[Parse JSON file]
Parse -->|invalid| E400[400 Bad Request]
Parse -->|OK| Find[Search three collections by race_id]
Find -->|not found| E404[404 Not Found]
Find -->|found| Save[Store prediction]
Save --> Success[200 OK]
Route ordering
/races/prediction is declared before /races/{race_id}. Declaring it later would make
prediction be interpreted as a race_id.
GET /v1/races
Overview
Lists races for the given racing type.
The list also returns the status of the latest article for each race (article_status). It backs
the status badge in the admin list; fetching it per race would be an N+1, so it is resolved in bulk.
Request and Response
URI
Query parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| type | enum | โฏ | auto_racing / bicycle_racing / horse_racing |
| page | number | Page number. Default: 1 |
|
| size | number | Items per page. Default: 10 |
|
| order | enum | asc / desc (by name). Default: desc |
Ordering is by name
Unlike other resources, the race list sorts by name (e.g. 20260504_้ฃฏๅก_05R). Because the
name starts with the date, lexical order equals race-date order.
Response
200 OK
{
"list": [
{
"id": "string",
"type": "horse_racing",
"name": "string",
"race_result": {},
"prediction": null,
"article_status": "draft",
"created_at": 1234567890000,
"updated_at": 1234567890000
}
],
"current_page": 1,
"total_count": 10
}
Error handling
| Description | Status code | Status |
|---|---|---|
type missing or invalid |
422 | Unprocessable Entity |
| Internal server error | 500 | Internal Server Error |
GET /v1/races/{race_id}
Overview
Fetches a single race. race_id is unique across racing types, so no type is required.
Request and Response
URI
| Path parameter | Type | Notes |
|---|---|---|
| race_id | string | The race _id |
Response
200 OK
Returns the race schema (article_status is null).
Error handling
| Description | Status code | Status |
|---|---|---|
| Race not found | 404 | Not Found |
| Internal server error | 500 | Internal Server Error |
PUT /v1/races/{race_id}
Overview
Updates a race.
Request and Response
Headers
Request
Content-Type: application/json
URI
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | Race name | |
| race_result | object | Contents of the race result JSON |
Fields left out (null) are not updated. prediction cannot be updated here โ use
PUT /v1/races/prediction.
Response
200 OK
Returns the updated race schema.
Error handling
| Description | Status code | Status |
|---|---|---|
| Invalid request body | 422 | Unprocessable Entity |
| Race not found | 404 | Not Found |
| Internal server error | 500 | Internal Server Error |
DELETE /v1/races/{race_id}
Overview
Soft-deletes a race.
Request and Response
URI
Response
204 No Content
Error handling
| Description | Status code | Status |
|---|---|---|
| Race not found | 404 | Not Found |
| Internal server error | 500 | Internal Server Error |
Processing flow
flowchart TD
Start([DELETE /v1/races/race_id]) --> Find[Search three collections by race_id]
Find -->|not found| E404[404 Not Found]
Find -->|found| Delete[Set deleted_at]
Delete --> Success[204 No Content]