Skip to content

Announce the files of many results at once — step 1 of 3, up to 100 files a request

POST
/v1/files/uploads-batch
curl --request POST \
--url https://beta-api.plune.ai/v1/files/uploads-batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "items": [ { "resultId": "6f1c2b3a-0000-4000-8000-0000000000e1", "name": "signed-in.png", "contentType": "image/png", "size": 48213 }, { "resultId": "6f1c2b3a-0000-4000-8000-0000000000e1", "name": "trace.zip", "contentType": "application/zip", "size": 3145728 } ] }'

POST /v1/results/{id}/files/uploads for a whole report: { "items": [{ "resultId", "name", "contentType", "size" }] }, at most 100 items, each for any result of the caller’s project (ADR 0051, #1476). The answer is 200 with one entry per item, in order, whatever the items said: status: "upload" (a link, as the single route gives it — PUT the bytes, then complete), "direct" (the project has no bucket and the file fits the built-in storage: send it by POST /v1/files/direct-batch), "exists" (the result already has this file, finished) or "refused" with a code and the httpStatus the single route would have answered with. One item refused costs the others nothing, and nothing is reserved for it. Someone else’s result, an invented id and a result in the Bin are the same not_found for that item. What a retry is: an item may carry sha256 (64 hex characters, of the bytes it will PUT; Plune cannot read the bucket’s bytes, so it is the client’s word). With it, a finished file of the result with the same name, content type and sha256 answers exists — nothing to send — and the same pair twice in one request is duplicate for the second. Without it, a finished file is never taken for this one: two files of one name, type and size are two files. A file announced and not finished is given a fresh link — upload with resumed: true, the same id — only to the user who announced it, only while a new link is still worth a round trip (the first 45 minutes: complete refuses a file after an hour, and a link lives 15 minutes), and only when the item carries a sha256 equal to the row’s and the name, type and size are the same (an item without one always gets a row of its own; a retry of such an item can leave a stale waiting row, which the nightly sweep of unfinished uploads removes); otherwise the item gets a row and a link of its own. A result takes 30 files and a file the ceilings of the bucket, as for the single route; each item is judged alone, in order, so the files that fit are taken. Counted with the other two batch routes: 6000 items a minute a person and 120 requests a minute, each per server instance (429 rate_limited with Retry-After, nothing taken). A reader of the project cannot use it. The single routes stay as they are. A platform that has this route says so with the header X-Plune-Files-Batch: 1 on the answers of POST /v1/results/{id}/files/uploads, POST /v1/results/{id}/files and POST /v1/files/{id}/complete; an older one answers this route 404.

Media type application/json
object
items
required
Array<object>
>= 1 items <= 100 items
object
resultId
required

A result of the caller’s project in an open run.

string
name
required
string
contentType
required
string
size
required
integer
>= 1
sha256

Optional: SHA-256 of the bytes. Makes the item a retry of a finished file of the same name with the same hash; without it nothing finished is ever taken for it.

string
/^[0-9a-fA-F]{64}$/
Example
{
"items": [
{
"resultId": "6f1c2b3a-0000-4000-8000-0000000000e1",
"name": "signed-in.png",
"contentType": "image/png",
"size": 48213
},
{
"resultId": "6f1c2b3a-0000-4000-8000-0000000000e1",
"name": "trace.zip",
"contentType": "application/zip",
"size": 3145728
}
]
}

One entry per item. Shown: the two files of the request, for a project with a bucket (bucket) and without one (noBucket)

Media type application/json
object
items
required

One entry per item, in order.

Array<object>
object
index
required

The item’s position in the request, from 0.

integer
status
required
string
Allowed values: upload direct exists refused
code

For refused, why: invalid_name, invalid_content_type, invalid_file, not_found, run_closed, file_too_large, too_many_files, duplicate, no_storage, over_budget, platform_not_configured, not_connected, bucket_unreachable, file_mismatch, file_missing, upload_expired.

string
error
string
httpStatus

For refused: the status the single route answers with for the same cause.

integer
retryable

The same item may succeed if sent again unchanged.

boolean
id
string
name
string
contentType
string
size
integer
url

For upload, as in ResultFileUploadLink: a right to write that one object — do not log it.

string
method
string
Allowed values: PUT
headers
object
key
additional properties
string
expiresAt
string format: date-time
resumed

For upload: the file was announced before — same id, a new link.

boolean
Examples

The project has a bucket

{
"items": [
{
"index": 0,
"status": "upload",
"id": "3f0c2a7e-1b5d-4c8e-9a61-2d7f4e8b0c13",
"name": "signed-in.png",
"url": "https://s3.eu-central-1.amazonaws.com/acme-results/plune/…?X-Amz-Signature=…",
"method": "PUT",
"headers": {
"content-type": "image/png",
"content-length": "48213"
},
"expiresAt": "2026-10-15T09:15:00.000Z"
},
{
"index": 1,
"status": "refused",
"code": "file_too_large",
"error": "'trace.zip' is 209715200 bytes; a trace file may be at most 100 MiB",
"httpStatus": 413
}
]
}

A body that is not { items: [{ resultId, name, contentType, size }] } with 1 to 100 items

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

No / invalid token or session

A reader of the project

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

Rate-limited: the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442; the body names the plan, #2092). Retry-After carries the seconds until the window rolls over.

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

Seconds until the window rolls over