The three APIs the DataOps server implements
Requests and responses of the list · manifest · file endpoints, authentication, error format, and accepted field aliases
When you register one base URL with the platform, the platform calls three paths under it. In the examples, https://data.example.com/api is the base URL.
| # | Method · path | Purpose |
|---|---|---|
| 1 | GET {base}/datasets | Dataset list + update time |
| 2 | GET {base}/datasets/{id}/manifest | File list + SHA-256 — the only basis for detecting changes |
| 3 | GET {base}/datasets/{id}/files/{file_id} | File body (binary) |
The paths must have exactly this shape. In return, the field names in responses can follow your own conventions — the platform accepts several names with the same meaning (see Field aliases below).
Common conventions
| Item | Requirement |
|---|---|
| Authentication | A single Authorization: Bearer <token> header. For a server without a token, leave the token field empty when you register it. The platform has to present this token, so it stores it in the clear. Issue a read-only token dedicated to the platform |
| Transport | HTTPS recommended. The platform accepts only http:// · https:// and rejects link-local (169.254.x.x) and multicast IP addresses |
| Ids in paths | {id} · {file_id} are percent-encoded into the path (/ becomes %2F). Ids made of letters, digits, -, _ and . avoid encoding issues |
| Response format | 1 · 2 return an application/json (UTF-8) object; 3 returns binary |
| Times | ISO 8601 with a time zone (for example 2026-07-21T09:30:00Z). Without a time zone, the time is read as UTC |
| Errors | HTTP status code + JSON body. 401 for authentication failure, 404 for a missing resource. For list and manifest calls, the first 200 characters of the error body go into the on-screen message as is (file calls show only the status code) |
| Call frequency | Called only when a person presses a button (no scheduled sync) |
| Time limit | Each request must start responding within 30 seconds. Large files are timed per read, so a long transfer is fine |
1. Dataset list
curl -H "Authorization: Bearer $TOKEN" \
"https://data.example.com/api/datasets?page=1&page_size=50&updated_after=2026-07-01T00:00:00Z"
{
"items": [
{
"id": "ds-inspect-2026w29",
"name": "Line inspection images 2026-W29",
"modality": "image",
"file_count": 412,
"size_bytes": 1073741824,
"updated_at": "2026-07-21T09:30:00Z"
}
],
"page": 1,
"total": 3
}
| Field | Required | Description |
|---|---|---|
id | Required | A unique ID that never changes (256 characters or fewer). It must stay the same even if the name changes — it is the key the platform uses to find what to sync again. If any entry lacks an id, the whole list fails |
name | Recommended | Name shown in the remote dataset chooser. id is used if missing |
modality | Recommended | image · pointcloud · timeseries · tabular. Becomes the default modality when a new dataset is created on screen |
file_count · size_bytes | Recommended | For display on the selection screen |
updated_at | Recommended | Must change whenever a file is added, changed or deleted. Shown as the "updated" time in the chooser, and the basis for updated_after queries |
- The platform sends
page(starting at 1).page_sizeis added only by the connection test (page=1&page_size=1) and when a caller of the platform API supplies it. The on-screen remote dataset list sends onlypage. - If you split the list into pages, always return
total. The screen asks for the nextpagethrough "Load more" only while it has loaded fewer items thantotal. Withouttotalit treats the first response as the whole list, so later pages never show. If you do not paginate, ignorepage·page_sizeand return everything. - When
updated_afterarrives, return only what changed after that time (recommended). The screen does not send it; it is passed on, in UTC (…Z), only when a script callsGET /api/v1/external-sources/{id}/remote-datasets?updated_after=…. - A sync does not call the list. It uses only the manifest and file paths.
2. Manifest
curl -H "Authorization: Bearer $TOKEN" \
"https://data.example.com/api/datasets/ds-inspect-2026w29/manifest"
{
"dataset_id": "ds-inspect-2026w29",
"name": "Line inspection images 2026-W29",
"modality": "image",
"updated_at": "2026-07-21T09:30:00Z",
"annotation_format": "coco",
"classes": [{ "id": 0, "name": "defect_a" }, { "id": 1, "name": "defect_b" }],
"files": [
{
"file_id": "f-0001",
"filename": "20260721_line3_0001.png",
"kind": "image",
"size_bytes": 2493833,
"sha256": "9f8a1c…",
"meta": { "line": "line-3", "measured_at": "2026-07-21T09:30:12+09:00" }
}
]
}
| Field | Required | Description |
|---|---|---|
name | Recommended | Used as the name when a new dataset is created with the name left empty. Without it, the remote id becomes the name. Keep it the same as the list's name |
modality | Optional | Used when a dataset is created through the API without a modality |
updated_at | Recommended | After a sync ends, shown as the "remote update time" on the external source card |
files[].file_id | Required | A file key used in the download path that never changes. It must stay the same even when the file's contents change (only then is it judged "changed" and the same row replaced) |
files[].filename | Required | File name including the extension. The platform decides the file kind from the extension. A path (a/b.png) is dropped and only the last name (b.png) is used, so the same name in different folders clashes and the later one is skipped |
files[].sha256 | Effectively required | SHA-256 of the body (hex, case-insensitive). Without it, that file is downloaded again every time |
files[].size_bytes | Recommended | For progress display |
files[].kind | Optional | Used only for CSV · TSV · Parquet files: timeseries or tabular classifies the file as that. The kind of other files is decided by the extension |
files[].meta | Optional | Per-file metadata. Kept as is in the platform file's meta.source |
annotation_format · classes | Optional | Only recorded, not interpreted. Platform validators read labels directly from the annotation files received |
If any entry lacks a file_id or filename, the whole manifest cannot be read and that sync fails.
3. File body
curl -H "Authorization: Bearer $TOKEN" -o 0001.png \
"https://data.example.com/api/datasets/ds-inspect-2026w29/files/f-0001"
- Stream the file body as is. Any
Content-Typeworks. - Redirects are followed, so you may return a 302 to a presigned URL instead of the body. The
Authorizationheader is not sent to a different host, so the presigned URL must work without the token. - Resuming (
Range) is not used yet. A failed file is fetched again in full on the next sync. - Each file can be up to 2 GiB (configurable in the server settings). A larger file is cut off mid-transfer and recorded as a warning.
Error responses
// 401
{ "error": "invalid_token", "message": "The token has expired." }
// 404
{ "error": "dataset_not_found", "message": "ds-xxxx was not found." }
// 429
{ "error": "rate_limited", "message": "Please try again later." }
The platform does not interpret this body; it shows the start of it to the user. It must contain one sentence that states the cause.
The platform does not retry an error response and does not read Retry-After. If a file call fails, only that file is recorded as a warning and it is fetched again on the next sync. If the manifest call fails, that sync fails.
Field aliases
You can use any of the names below for response fields. The platform looks for them in order and uses the first non-empty value.
| Meaning | Accepted names |
|---|---|
| List array | items · datasets · results · data |
| Total count | total · count · total_count |
| Dataset id | id · dataset_id · datasetId |
| Name | name · title |
| Modality | modality · type |
| File count | file_count · fileCount · files |
| Size | size_bytes · sizeBytes · size |
| Update time | updated_at · updatedAt · modified_at · last_modified (for the manifest, updated_at · updatedAt) |
| File array | files · items · entries |
| File id | file_id · fileId · id |
| File name | filename · name · path |
| Checksum | sha256 · checksum_sha256 · checksum · hash |
| File kind | kind · type |
| Class table | classes · categories · labels |
| Annotation format | annotation_format · annotationFormat · label_format |
If you really need a name not in this table, ask the platform team to add an alias.
Supported file formats
| Kind | Formats |
|---|---|
| Images | PNG · JPG · BMP · TIFF · WebP |
| Time series · tables | CSV · TSV · Parquet (a header in the first row is required) |
| Point clouds | PLY — if there is a per-point integer label property, it is found automatically and classes are counted |
| Annotations | COCO JSON · LabelMe JSON · VOC XML |
The extension cannot tell CSV · TSV · Parquet time series from tables. If the manifest gives a per-file kind of timeseries or tabular, the platform follows it. Otherwise the dataset modality decides (tabular gives tables, anything else time series); in that case report modality as tabular in the list, or choose the modality directly when the dataset is created.
Integration checklist (provider side)
Before registering with the platform, call the three paths yourself with curl. The commands are the same as "Try it" in the Reference implementation. The platform's Test connection (연결 확인) calls only the list, so check the manifest and file paths here.
| # | Check | If it fails |
|---|---|---|
| 1 | Does the list return 200 with the token? | Fix the authentication method first |
| 2 | Does every list and manifest entry have an id · file_id · filename? | The whole list or manifest cannot be read |
| 3 | If you paginate, is total present? | Only the first page shows on screen |
| 4 | Does updated_after work? | Use full listing instead of incremental queries |
| 5 | Does the sha256 in the manifest match sha256sum of the downloaded file? | The file is dropped with a warning |
| 6 | Do received files open without corruption? | Check the transport layer |
| 7 | If the same dataset is synced twice, does the second sync fetch 0 files? | Incremental sync does not work (check whether file_id changes) |