API reference
Uploads
The Uploads API moves CAD and drawing files into ARCNM.
The Uploads API moves CAD and drawing files into ARCNM: presign a direct upload, confirm it, then list, retry, cancel, or fetch a download URL.
Auto-generated from the public OpenAPI spec — this page never drifts from the running API. Base URL
https://api.arcnm.io. Authenticate with theX-API-Keyheader (see Authentication).
List Uploads
GET /api/v1/uploads/
List the org's uploads, hiding cancelled rows by default.
Cancelled uploads are useless — the file may have been written to
storage but the download endpoint refuses to serve it (status gate
is confirmed only). They just clutter the picker, so the UI gets
them filtered out unless an admin explicitly opts in via
?include_cancelled=true (e.g. for an audit view).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_cancelled |
query | boolean | no | Include cancelled uploads in the list (hidden by default). |
Request
curl -X GET https://api.arcnm.io/api/v1/uploads/ \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/uploads/",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/", {
method: "GET",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Cancel Upload
POST /api/v1/uploads/{data_source_id}/cancel
Mark a pending upload as cancelled.
Idempotent for already-cancelled rows; rejects rows that have moved
past pending since cancelling a confirmed file would silently
delete a valid artefact.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
data_source_id |
path | string | yes | Identifier of the data source. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/{data_source_id}/cancel \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/{data_source_id}/cancel",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/{data_source_id}/cancel", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
content_type |
string | MIME type of the file (e.g. 'application/pdf'); null if unknown. |
created_at |
string | ISO 8601 timestamp when the upload record was created. |
id |
string | Unique identifier of the uploaded data source. |
name |
string | Original filename of the uploaded file. |
sha256 |
string | Hex-encoded SHA-256 checksum of the file; null until confirmed. |
size_bytes |
integer | Size of the file in bytes; null until the upload is confirmed. |
status |
string | Upload lifecycle state: pending, confirmed, failed, or cancelled. |
Example response
{
"content_type": "string",
"created_at": "2026-06-01T12:00:00Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800,
"status": "string"
}
Download Url
GET /api/v1/uploads/{data_source_id}/download-url
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
data_source_id |
path | string | yes | Identifier of the data source. |
Request
curl -X GET https://api.arcnm.io/api/v1/uploads/{data_source_id}/download-url \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/uploads/{data_source_id}/download-url",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/{data_source_id}/download-url", {
method: "GET",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
url |
string | Short-lived presigned URL to download the confirmed file. |
Example response
{
"url": "string"
}
Retry Upload
POST /api/v1/uploads/{data_source_id}/retry
Re-queue the confirm worker for a non-confirmed upload.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
data_source_id |
path | string | yes | Identifier of the data source. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/{data_source_id}/retry \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/{data_source_id}/retry",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/{data_source_id}/retry", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
content_type |
string | MIME type of the file (e.g. 'application/pdf'); null if unknown. |
created_at |
string | ISO 8601 timestamp when the upload record was created. |
id |
string | Unique identifier of the uploaded data source. |
name |
string | Original filename of the uploaded file. |
sha256 |
string | Hex-encoded SHA-256 checksum of the file; null until confirmed. |
size_bytes |
integer | Size of the file in bytes; null until the upload is confirmed. |
status |
string | Upload lifecycle state: pending, confirmed, failed, or cancelled. |
Example response
{
"content_type": "string",
"created_at": "2026-06-01T12:00:00Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800,
"status": "string"
}
Bulk Cancel Uploads
POST /api/v1/uploads/bulk-cancel
Cancel many pending uploads at once.
Per-row failures are reported in skipped so the UI can render a
summary toast — the whole batch isn't rolled back if a single id is
already confirmed. We cap the batch size to keep a single request
bounded.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Upload (data source) IDs to cancel or retry in bulk. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/bulk-cancel \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/bulk-cancel",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/bulk-cancel", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
cancelled |
string[] | Upload IDs that were successfully cancelled. |
retried |
string[] | Upload IDs that were successfully re-queued for confirmation. |
skipped |
object[] | Uploads that were skipped, each as {id, reason}. |
Example response
{
"cancelled": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"retried": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
{}
]
}
Bulk Retry Uploads
POST /api/v1/uploads/bulk-retry
Re-queue many uploads at once.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Upload (data source) IDs to cancel or retry in bulk. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/bulk-retry \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/bulk-retry",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/bulk-retry", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
cancelled |
string[] | Upload IDs that were successfully cancelled. |
retried |
string[] | Upload IDs that were successfully re-queued for confirmation. |
skipped |
object[] | Uploads that were skipped, each as {id, reason}. |
Example response
{
"cancelled": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"retried": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
{}
]
}
Confirm
POST /api/v1/uploads/confirm
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
data_source_id |
string | yes | UUID of the presigned upload to validate and ingest. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/confirm \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/confirm",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/confirm", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
job_id |
string | Identifier of the background job validating and ingesting the file. |
Example response
{
"job_id": "string"
}
Presign
POST /api/v1/uploads/presign
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
content_type |
string | no | MIME type of the file (e.g. application/pdf). |
name |
string | yes | Original filename of the file to upload. |
size_bytes |
integer | no | Size of the file in bytes, if known. |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/presign \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/uploads/presign",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"name": "string"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/presign", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "string"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
data_source_id |
string | Identifier of the data source record; pass it to /uploads/confirm. |
expires_in |
integer | Seconds until the presigned URL expires. |
fields |
object | Form fields to include for a multipart POST upload; null if none. |
headers |
object | Headers that must be sent with the upload request; null if none. |
method |
string | HTTP method to use against url (typically PUT or POST). |
url |
string | Presigned URL the client uploads the file bytes to. |
Example response
{
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"expires_in": 0,
"fields": {},
"headers": {},
"method": "string",
"url": "string"
}