What you are trying to keep
A failing pipeline leaves a test log, a coverage report or a compiled binary on a runner that disappears when the job ends. Most CI platforms have an artifact feature, but it ties retrieval to that platform's login and to the run that produced the file. When a colleague on another team wants the log, or a later pipeline wants yesterday's binary, the practical need is a place that accepts bytes over HTTP, holds them for a few days and then lets go.
That need has four parts: a way to write from inside the job, a retention period that ends without anyone cleaning up, a way for a reader to fetch the file without an account on your CI system, and a way to tell two runs of the same job apart. The rest of this guide takes those parts in order and ends with the situations where this approach is the wrong choice.
Upload the file from the job
The operation is already defined by HTTP itself. In RFC 9110 a PUT stores a representation at a target address, and curl sends a file untouched when you use --data-binary. The write credential belongs in your CI platform's masked secret store, so the job reads it from there and sends it in the Authorization header. During the private trial a new space is created by invitation, so these examples assume you already hold a write capability; the policy centre describes the current status.
curl -fsS -X PUT \
-H "Authorization: Capability <write-capability>" \
-H "Content-Type: text/plain" \
--data-binary @build.log \
https://in.svc.nz/<box>/build.log
The -f flag makes curl exit with a failure status on an HTTP error, so a rejected upload fails the step instead of passing silently. The -sS pair hides the progress meter but still prints an error message. The JSON response carries the version number that was just created, the time at which the file expires and a safe link to the latest copy. The response also contains credentials, so keep it out of the job log.
The file name is yours to choose, and a name that includes the pipeline or branch keeps unrelated artifacts apart. Use a separate name for each kind of output, such as the log, the coverage report and the binary, rather than packing them into one file. For several files from one run, archive them into a single tarball before the upload so that a reader fetches one coherent set.
How long it lives
An anonymous space expires on its own, and the clock counts from the most recent upload to it. Writing a new artifact therefore moves the expiry forward, while a space nobody writes to just ends. Once it has expired, normal access through the API stops. In a run against a local copy, a second upload to the same name returned a later expiry time than the first.
You can ask for a shorter life when the artifact is only needed briefly. With the downloadable client, svcbox share build.log --ttl 259200 uploads the file and requests an expiry of 259200 seconds, which is three days, then prints the address, the header to send and the expiry time. Read that expiry from the response instead of assuming it, because the service decides the final value and may apply its own cap.
Expiry ends access without being deletion, because the service does not promise that the underlying bytes have been erased. If a log can contain tokens or customer data, encrypt it in the job before the upload and keep the key somewhere other than the artifact. Size and request-rate limits also apply to uploads, so check what the service reports before sending a very large file, and compress logs that run to many megabytes.
How a teammate or a later step fetches it
A reader needs a read capability for the same space, which is a weaker permission than the write one. Hand that read capability to the teammate directly, or store it in the secret store of the pipeline that consumes the file. The download is another single request:
curl -fsS \
-H "Authorization: Capability <read-capability>" \
-o build.log \
https://out.svc.nz/<box>/build.log/latest
The word latest returns whatever was uploaded most recently, which suits a teammate who wants the newest failing log. Against a local server, a request with no capability received a 401 response, and a read capability was refused when it tried to write. Keep the capability in the header and out of the address, because addresses end up in shell history, proxy logs and chat messages.
The same download works in a later job of the same pipeline, in a nightly pipeline or on a laptop. None of them needs the CI platform's login, only an HTTP client and the read capability. For a deploy job that must promote the exact binary that passed its tests, fetch a numbered version instead of the latest, as the next section describes.
What happens when the same name is written again
Every upload to a name creates a new version and leaves the earlier ones readable. The first run produces version 1, the second produces version 2 and so on, so a rerun never destroys the evidence from the run before it. This is how you keep both the failing log and the passing log from consecutive attempts.
| Run | Version created | Fetch with latest | Fetch by number |
|---|---|---|---|
| Run 1 | 1 | Version 1 until run 2 uploads | v1 always |
| Run 2 | 2 | Version 2 until run 3 uploads | v2 always |
| Run 3 | 3 | Version 3 | v3 always |
To fetch one run's file, put its version in place of latest, for example .../build.log/v2. The client lists them for you: svcbox versions <box> build.log prints each version with its size, content type and upload time, and svcbox get <box> build.log v2 --out build.log downloads one.
When this is the wrong tool
Use your CI platform's own artifact feature when the files should be tied to a run, visible in its interface and cleaned up by its retention settings. Choose a proper object store when you need retention beyond a few days, access logs, per-person permissions or a legal record of who downloaded what. Release binaries that customers depend on should live somewhere durable with backups, because an expiring space should never be the only copy of anything you cannot rebuild.
Two further limits are worth knowing. A capability is a bearer secret, so anyone who copies it from a log can use it until the space expires. Expiry also limits only the lifetime of access, not the contents. Treat the space as a short-lived handoff shelf for files you could regenerate, upload a small test file from a throwaway job first, and read the expiry time in the response before you rely on it.
FAQ
Can a later pipeline run fetch the file a different job uploaded?
Yes, when that run holds a read capability for the same name. Fetching latest returns the newest upload, and a numbered version returns exactly one earlier upload.
Does expiry delete the file?
Expiry ends normal access through the API. It is not a promise that the underlying bytes are erased, so encrypt anything sensitive before it leaves the job.
What if the same artifact name is uploaded twice?
The second upload becomes a new version and the first stays readable by its number. Nothing is overwritten, and the later upload also pushes the expiry time forward.
Should the capability go in the job log?
It should stay out of the log. Keep it in the CI platform's masked secret store, send it only in the Authorization header and avoid printing it, because job logs are usually readable by more people than the artifact.
Sources
HTTP Semantics, the standard reference; curl man page; OWASP Logging Cheat Sheet.