POST/artifact_storage/uploads
Create an upload
Costs one write, and returns the upload. The artifact's storage is metered once it's ready. A single upload of at most 8 MiB returns its presigned PUT in grant: send the bytes straight to S3 with every header of required_headers, then complete the upload. A larger upload is multipart: register each part to get its URL. If Motor can't immediately confirm multipart creation at S3, the response is 202 with an operation, and the upload's provider_state is creating until a reconciliation finishes it. Costs one write. Storage is metered for max(size, 128 KiB) bytes while the artifact is ready.
| Parameter | Location | Description |
|---|---|---|
| Idempotency-Key | header | 1 to 128 printable ASCII characters: a new value for each operation, and the same value when you retry it. Records last 35 days. |
| Motor-Mode | header | Artifact storage is live-only: omit it or send live. |
{
"size_bytes": 3,
"content_type": "text/plain",
"filename": "result.txt",
"sha256": "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=",
"expires_at": "2026-10-24T09:00:00Z",
"run_id": "run_42",
"metadata": {
"stage": "final"
}
}| Field | Type | Description |
|---|---|---|
size_bytesrequired | integer | The exact size: 0 to 53,687,091,200,000 bytes (10,000 parts of 5 GiB). |
sha256 | string | The Base64 of a 32-byte SHA-256 digest, as S3's x-amz-checksum-sha256 takes it. |
content_type | string | A media type of at most 127 bytes. Default application/octet-stream. |
filename | string | A label of at most 255 bytes, not a path. |
metadata | object | At most 16 string pairs: keys of 1 to 64 UTF-8 bytes, values of at most 256, and at most 4 KiB in all. Metadata never grants access. |
run_id | string | At most 128 bytes, for the list's filter. |
expires_at | When Motor deletes the artifact, a time in the future. Leave it out or send null to keep the artifact until you delete it. | |
lifetime_seconds | integer | The first PUT URL's lifetime. Default 60. |
rate_version | string | The rates that you expect; an older one is refused with 409 rate_changed. |
Responses
201The upload, with its first PUT for a single upload.
{
"id": "aup_5dMQVQFzdmYHPBV3rcQ25H",
"object": "upload",
"artifact_id": "art_MAhBep7JB6wMAsAY63cEPM",
"status": "open",
"transport": "single",
"size_bytes": 3,
"part_size_bytes": 3,
"part_count": 1,
"content_type": "text/plain",
"provider_state": "ready",
"observed_at": null,
"operation_id": null,
"expires_at": "2026-10-18T09:00:00.000Z",
"artifact_expires_at": "2026-10-24T09:00:00.000Z",
"last_url_expires_at": "2026-10-17T09:01:00.000Z",
"version": "1",
"etag": "aup_5dMQVQFzdmYHPBV3rcQ25H:1",
"created_at": "2026-10-17T09:00:00.000Z",
"completed_at": null,
"grant": {
"id": "agr_4m6yyjwaqkpJfPwCB62bV2",
"object": "grant",
"method": "PUT",
"url": "https://motor-galadriel-blobs-prod.s3.us-east-1.amazonaws.com/artifact_storage/content/42/art_MAhBep7JB6wMAsAY63cEPM/body?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20261017%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20261017T090000Z&X-Amz-Expires=60&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost%3Bif-none-match%3Bx-amz-checksum-sha256%3Bx-amz-server-side-encryption&x-motor-grant=agr_4m6yyjwaqkpJfPwCB62bV2&X-Amz-Signature=5b1f6c3e0a8d4e2f9c7b1a0d3e6f8a2b4c6d8e0f1a3b5c7d9e1f3a5b7c9d1e3f",
"required_headers": {
"content-length": "3",
"content-type": "text/plain",
"if-none-match": "*",
"x-amz-checksum-sha256": "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=",
"x-amz-server-side-encryption": "AES256"
},
"expires_at": "2026-10-17T09:01:00.000Z",
"size_bytes": 3,
"part_number": 1
},
"operation": null,
"cost": {
"rate_version": "2026-10-22",
"charged_usd": "0.00003",
"meter_quantities": {
"writes": 1,
"reads": 0,
"storage_byte_days": 0,
"delivery_bytes": 0
}
}
}202A multipart upload that Motor is still creating at S3. Poll the upload or the operation.
400The body isn't valid JSON, or an Idempotency-Key is missing.
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Expected a JSON value at byte 0.",
"retryable": false
}
}402The work would pass the paying key's spending limit, or your team doesn't have a payment card.
{
"error": {
"code": "spending_limit",
"message": "This work would exceed the paying API key's monthly spending limit. Raise the limit on the API keys page, or wait until the next billing month starts at 00:00 UTC.",
"retryable": false
}
}401The key is missing, invalid, or revoked.
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked.",
"retryable": false
}
}403A publishable key, a key that can't use Artifact storage, or a suspended team.
{
"error": {
"code": "forbidden",
"message": "This publishable key cannot access this API. Check the key's allowed APIs. For server-side requests, use the secret key.",
"retryable": false
}
}409A state, idempotency, rate, or reference conflict, such as upload_completed, artifact_in_use, idempotency_conflict, or rate_changed.
{
"error": {
"code": "upload_completed",
"message": "The upload is complete. Use its artifact.",
"artifact_id": "art_MAhBep7JB6wMAsAY63cEPM",
"retryable": false
}
}413The request body is larger than 64 KiB.
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 65536 bytes.",
"retryable": false
}
}415The body isn't application/json.
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json.",
"retryable": false
}
}422A field, the cursor, or Motor-Mode isn't valid.
{
"error": {
"code": "invalid_field",
"message": "`status` must be uploading, verifying, ready, deleting, deleted, failed, expired, or aborted.",
"retryable": false
}
}429Too many requests; retry after Retry-After.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds.",
"retryable": true,
"retry_after_seconds": 1
}
}503The provider or Motor is temporarily unavailable, or download URLs are paused. Wait for Retry-After when present; otherwise retry after a few seconds.
{
"error": {
"code": "provider_unavailable",
"message": "Artifact storage isn't configured in this environment.",
"retryable": true,
"retry_after_seconds": 5
}
}