API reference
Uploads
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…
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, newest first, 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).
The response is a plain array, so the page position travels in the
Link (RFC 8288) and X-Next-Cursor / X-Has-More response headers:
follow Link rel="next" until it stops being sent.
Paginated. Pass
cursor(from the previous response) to fetch the next page;limitcaps the page size.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_cancelled |
query | boolean | no | Include cancelled uploads in the list (hidden by default). |
cursor |
query | string | no | Opaque position token from the previous page's next_cursor (or the Link / X-Next-Cursor response header). Omit it for the first page. Keep every other query parameter identical for the whole walk — a cursor replayed against different filters is rejected. |
limit |
query | integer | no | Maximum rows to return in one page. |
order |
query | asc | desc |
no | Sort direction over the collection's ordering key. Use asc to reconcile a batch: rows come oldest-first, so work created while you page lands after your position instead of shifting rows under it. |
created_after |
query | string | no | Only rows created at or after this instant (RFC 3339, e.g. 2026-07-20T09:00:00Z). Inclusive. |
created_before |
query | string | no | Only rows created strictly before this instant (RFC 3339). Exclusive, so an after/before pair tiles a range without overlap. |
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. |
Delete Upload
DELETE /api/v1/uploads/{data_source_id}
Delete an upload: storage object (best-effort) plus the row.
part_revision_dataset.data_source_id carries ondelete=CASCADE,
so any part/revision attachments of this file are removed with it —
the UI warns about exactly that before calling here. An upload URL
cannot be revoked: a file still sent through it after the delete is
removed once the URL has expired.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
data_source_id |
path | string | yes | Identifier of the data source. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/uploads/{data_source_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/uploads/{data_source_id}",
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}", {
method: "DELETE",
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 |
|---|---|---|
message |
string |
Example response
{
"message": "string"
}
Update Upload
PATCH /api/v1/uploads/{data_source_id}
Rename an upload and/or move it to a folder.
The folder lives in metadata_json["folder"] — sending folder: null
clears it, omitting the field leaves it untouched (model_fields_set
distinguishes the two). Renaming changes the display name only; the
storage key is immutable once presigned.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
data_source_id |
path | string | yes | Identifier of the data source. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
folder |
string | no | Folder name to file this upload under; null clears the folder. |
name |
string | no | New filename for the upload; omit to keep the current name. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/uploads/{data_source_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"folder": "string",
"name": "string"
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/uploads/{data_source_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"folder": "string",
"name": "string"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/uploads/{data_source_id}", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"folder": "string",
"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. |
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. |
folder |
string | User-assigned folder name for organising files; null if unfiled. |
id |
string | Unique identifier of the uploaded data source. |
linked_datasets |
integer | Number of part revision dataset attachments that reference this file. |
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, rejected (refused when checked: content not of its declared type, larger than declared, or past the plan's storage), failed, or cancelled. |
system_folder |
string | Folder assigned by the platform: 'parts_uploads' for files that belong to a part, revision, or calculation workflow; null otherwise. |
Example response
{
"content_type": "string",
"created_at": "2026-06-01T12:00:00Z",
"folder": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"linked_datasets": 0,
"name": "string",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800,
"status": "string",
"system_folder": "string"
}
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. |
folder |
string | User-assigned folder name for organising files; null if unfiled. |
id |
string | Unique identifier of the uploaded data source. |
linked_datasets |
integer | Number of part revision dataset attachments that reference this file. |
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, rejected (refused when checked: content not of its declared type, larger than declared, or past the plan's storage), failed, or cancelled. |
system_folder |
string | Folder assigned by the platform: 'parts_uploads' for files that belong to a part, revision, or calculation workflow; null otherwise. |
Example response
{
"content_type": "string",
"created_at": "2026-06-01T12:00:00Z",
"folder": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"linked_datasets": 0,
"name": "string",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800,
"status": "string",
"system_folder": "string"
}
Download Url
GET /api/v1/uploads/{data_source_id}/download-url
Get a short-lived link to download the original file of a processed upload. An upload that is still processing or failed answers 404.
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. |
folder |
string | User-assigned folder name for organising files; null if unfiled. |
id |
string | Unique identifier of the uploaded data source. |
linked_datasets |
integer | Number of part revision dataset attachments that reference this file. |
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, rejected (refused when checked: content not of its declared type, larger than declared, or past the plan's storage), failed, or cancelled. |
system_folder |
string | Folder assigned by the platform: 'parts_uploads' for files that belong to a part, revision, or calculation workflow; null otherwise. |
Example response
{
"content_type": "string",
"created_at": "2026-06-01T12:00:00Z",
"folder": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"linked_datasets": 0,
"name": "string",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800,
"status": "string",
"system_folder": "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. The batch size is capped on BulkUploadIds.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Upload (data source) IDs to cancel or retry in bulk (1–200). |
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 Delete Uploads
POST /api/v1/uploads/bulk-delete
Delete many uploads at once.
Mirrors bulk-cancel: per-row failures land in skipped instead of
rolling back the batch; the batch size is capped on BulkUploadIds.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Upload (data source) IDs to cancel or retry in bulk (1–200). |
Request
curl -X POST https://api.arcnm.io/api/v1/uploads/bulk-delete \
-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-delete",
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-delete", {
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 |
|---|---|---|
deleted |
string[] | Upload IDs that were successfully deleted. |
skipped |
object[] | Uploads that were skipped, each as {id, reason}. |
Example response
{
"deleted": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
{}
]
}
Bulk Retry Uploads
POST /api/v1/uploads/bulk-retry
Re-queue processing for several uploads at once, for example after a failure.
Uploads that cannot be retried or are busy are listed in skipped with the
reason.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Upload (data source) IDs to cancel or retry in bulk (1–200). |
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
Finish a direct upload once the file is sent: checks and keeps it.
Answers the upload's id and its status. The file is checked after this
call answers, so a first confirm answers pending; the upload can be
attached to a part revision
(POST /parts/{part_id}/revisions/{revision_id}/datasets) once it is
confirmed. Confirming again is safe and answers the current status.
A confirm before the file has arrived answers 409 upload_not_received
(send the file the way presign's method says, then confirm again); an
upload that was refused, could not be checked or was cancelled answers
409 upload_rejected, upload_failed or upload_cancelled.
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 |
|---|---|---|
data_source_id |
string | UUID of the confirmed upload, as presign returned it: the handle it is listed by in GET /uploads, with its status, and attached to a revision by. |
status |
pending | confirmed |
pending: the file arrived and is being checked; attaching it now answers 409 upload_pending with Retry-After, and calling confirm again answers the current status. confirmed: the file was checked and kept, and can be attached to a part revision. |
Example response
{
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "pending"
}
Presign
POST /api/v1/uploads/presign
Start a direct file upload: answers where and how to send the file.
Send the file to url the way method says — PUT: the raw bytes as
the body, with the headers named; POST: a multipart form with every
fields entry, then the file as the last field, named file — and then
call POST /uploads/confirm. Until it is confirmed, the upload reserves
its declared size_bytes (the most its file type allows, when none is
declared) of the plan's storage; if url expires with no file sent, the
reservation is released. This is the upload path for a file of any size,
and the one a client that can only send JSON (every MCP client) uses:
the multipart upload routes cannot carry a file in a JSON argument.
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. Until the upload is confirmed it reserves this much of the plan's storage (the most its file type allows, when omitted), and a larger file is refused. |
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 url stops accepting the file. |
fields |
object | Form fields to send, verbatim, ahead of the file in a POST; null for a PUT. |
headers |
object | Headers to send with a PUT; null for a POST. |
method |
PUT | POST |
How to send the file to url. PUT: the file's raw bytes as the request body, with exactly the headers named. POST: a multipart/form-data form with every entry of fields as a form field, followed by the file itself as the last field, named file. Always branch on this value: a file sent the other way is refused by storage and never arrives. |
url |
string | Presigned URL to send the file to, as method says. |
Example response
{
"data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"expires_in": 0,
"fields": {},
"headers": {},
"method": "PUT",
"url": "string"
}