Announce the files of many results at once — step 1 of 3, up to 100 files a request
const url = 'https://beta-api.plune.ai/v1/files/uploads-batch';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”object
object
A result of the caller’s project in an open run.
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.
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 } ]}Responses
Section titled “ Responses ”One entry per item. Shown: the two files of the request, for a project with a bucket (bucket) and without one (noBucket)
object
One entry per item, in order.
object
The item’s position in the request, from 0.
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.
For refused: the status the single route answers with for the same cause.
The same item may succeed if sent again unchanged.
For upload, as in ResultFileUploadLink: a right to write that one object — do not log it.
object
For upload: the file was announced before — same id, a new link.
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 } ]}The project has none: the picture goes to `direct-batch`, the trace has nowhere to go
{ "items": [ { "index": 0, "status": "direct", "name": "signed-in.png" }, { "index": 1, "status": "refused", "code": "no_storage", "error": "the project has no bucket, and this file does not fit the built-in storage", "httpStatus": 422 } ]}A body that is not { items: [{ resultId, name, contentType, size }] } with 1 to 100 items
object
Example generated
{ "error": "example"}No / invalid token or session
A reader of the project
object
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.
object
Example generated
{ "error": "example"}Headers
Section titled “Headers ”Seconds until the window rolls over