Attachments
Attach screenshots, logs, and trace files to an execution. The simplest way is a single call; a presigned flow is available for very large files.
One-call upload
POST /executions/{id}/attachments with multipart/form-data and a single file field. The API streams the bytes to object storage (MinIO/S3), links the file to the execution’s screenshots, and returns an attachment ref { key, filename, download_url }. One round trip, nothing else to do.
uploadAttachment call that performs this upload and returns the ref. The file is linked to the execution automatically, so you don’t need to pass the ref anywhere else. Use the raw API (the curl tab) if you’re uploading from a language we don’t ship an SDK for.Example
bytes, _ := os.ReadFile("test-screenshot.png")
ref, err := client.UploadAttachment(ctx, execID, "test-screenshot.png",
"image/png", bytes)
if err != nil { log.Fatal(err) }
log.Printf("uploaded: %s (%d bytes)", ref.URL, ref.Size)POST /runs/{run_id}/testcases/{test_case_id}/report is a single multipart call that records the result (result, duration_ms, worker_id, …) and links an optional file — handy when your runner only kept the run id + test case id. The SDKs expose it as reportResult.Large files — presigned upload
With the one-call upload the bytes flow through the API, which ties up a worker for the whole transfer. For very large artifacts (big browser traces, videos) use the presigned flow instead: the API hands back a short-lived URL and the runner PUTs the bytes straight to storage, so they never touch the API. Then link the ref to the execution via the result update.
POST /executions/{id}/attachments/presign→{ upload_url, key, download_url }.PUTthe bytes toupload_url(direct to MinIO/S3).PATCH /executions/{id}with ascreenshotsarray to link it.
EXEC_ID="<execution-id>"
HOST="https://YOUR_HOST"
# 1) Presign — ask the API for a short-lived upload URL.
resp=$(curl -sS -X POST "$HOST/api/v1/executions/$EXEC_ID/attachments/presign" \
-H "Authorization: Bearer $TCM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"filename":"test-screenshot.png","content_type":"image/png"}')
upload_url=$(echo "$resp" | jq -r .upload_url)
key=$(echo "$resp" | jq -r .key)
download_url=$(echo "$resp" | jq -r .download_url)
# 2) PUT the bytes straight to storage (MinIO/S3) — the API never sees them.
# Content-Type must match what you presigned with.
curl -sS -X PUT "$upload_url" \
-H "Content-Type: image/png" \
--data-binary @test-screenshot.png
# 3) Link the file to the execution via the result update.
curl -sS -X PATCH "$HOST/api/v1/executions/$EXEC_ID" \
-H "Authorization: Bearer $TCM_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"screenshots\":[{\"key\":\"$key\",\"filename\":\"test-screenshot.png\",\"download_url\":\"$download_url\"}]}"Limits
- Per-file size limit: 100 MB by default (configurable per tenant).
- Allowed content types: anything — TCM does not inspect the bytes.
- Attachments are tied to the execution; revoking the token does not delete the uploaded files.