# Keep files with a run

> Store a file in the runtime as an artifact, with the run it came from or in a project, and list, download and delete it.

This guide keeps a file in the runtime with the run it came from, then finds it, downloads it and deletes it.

A file kept in the runtime is an **artifact**, such as a report a run's output became, a document your app sent in, or an export. Each one belongs to a project, and can name the run it came from. Its name, type, size and sha256 are kept in the runtime's database. Its bytes are kept on disk or in a Google Cloud Storage bucket.

## Before you start

- **A runtime that keeps artifacts.** `kindgi dev` keeps them in the pack's `.kindgi/dev/artifacts`. Another runtime keeps them once you [turn them on](https://docs.kindgi.com/v0.3.0/guides/runs/keep-files-with-a-run.md#turn-them-on).
- **Kindgi 0.2's CLI**, and a file to keep, such as `report.pdf`.
- **A run's id,** to keep the file with that run.

## Upload one

Uploading the file keeps it with its run:

```sh
pnpm exec kindgi artifacts upload report.pdf --run=<run-id> --content-type=application/pdf --tag=kind=summary
```

- `report.pdf` is the file to upload.
- `--run=<run-id>` is the run it came from, where `<run-id>` is the run's id.
- `--content-type` is its type, `application/octet-stream` unless you say.
- `--tag=key=value` labels it. You can give it more than once.

It prints the artifact's record, with its `blobId`.

- **Its project:** with `--run`, it's the run's project. Without `--run`, it's `--project`'s, or else the tenant's Default project. A `--project` that isn't the run's is refused, and so is a run that doesn't exist, with `404 run-not-found`. With an API key limited to a project, an upload that names no project goes to the key's project. One whose project, named or its run's, is another is refused with `403 key-project-mismatch`.
- **Its name** is the file's, unless `--name` gives another.
- **Its size:** the runtime refuses a bigger upload than its cap, 100 MiB by default. It answers `413 artifact-too-large`, with the cap in `details.maxBytes`.

From your app, in TypeScript, `artifacts.upload` sends the file. `download` streams it back, and `head` reads what's kept without the bytes.

## Find and download them

Listing a run's artifacts gives each one's id, which you read and download it by:

```sh
pnpm exec kindgi artifacts list --run=<run-id> --table
pnpm exec kindgi artifacts get <blob-id>
pnpm exec kindgi artifacts download <blob-id> -o report.pdf
```

- `list` is newest first. `--run`, `--project` and `--content-type` narrow it, and `--table` prints a table instead of JSON.
- `<blob-id>` is an artifact's `blobId`, from the list.
- `get` gives an artifact's name, type, size and sha256, without its bytes.
- `download` writes the file under its own name in this directory, unless `-o` names one. It keeps a file that's already there, unless you add `--force`.
- `download` first writes to a new file next to the one it saves, and moves it into place only once the whole file has arrived and its sha256 is the one the server sent. A download that fails leaves no file, and with `--force` the file already there stays as it was.
- One Ctrl-C (or `SIGTERM`) stops `download`, whether it's still waiting for the server or part way through the file. As after a failure, it leaves no file, and a file already there stays as it was. It says `kindgi artifacts download stopped: no file was written.` and exits with code 130.
- Ended at once, by a second Ctrl-C or `kill -9`, `download` can leave that new file behind: the file's name, then 8 hex digits, then `.partial`, such as `report.pdf.1a2b3c4d.partial`. You can delete it.

A download is checked against the sha256 and size kept for the artifact. If its bytes were changed in their store outside the runtime, on disk or in the bucket, the download stops part way: the runtime closes the connection before the last of its `Content-Length` bytes. `download` then fails with `the download stopped part way (…): report.pdf wasn't written`, and writes no file. In TypeScript, reading `download`'s body throws. The runtime logs each one at `ERROR`, as `Artifact <blob-id>'s stored bytes aren't the ones put; the read was stopped`, with its tenant, where its bytes are kept, the sha256 and size kept for it, and what was read. The log line never holds the file's content.

With [authorization](https://docs.kindgi.com/v0.3.0/deploy/authorization.md) on, listing and downloading need `read` on the artifact's project, and uploading and deleting need `write`. An artifact you can't read answers `404`, as if it weren't there, and lists leave it out.

## Delete one

```sh
pnpm exec kindgi artifacts delete <blob-id>
```

`<blob-id>` is the artifact's `blobId`. It's gone from lists and downloads at once. Its record and bytes stay until a retention policy on `artifact` purges them, as [Keep and purge deleted data](https://docs.kindgi.com/v0.3.0/deploy/retention.md) describes.

## Turn them on

The runtime keeps artifacts once `KINDGI_ARTIFACTS` says where their bytes go:

- `local:<absolute directory>`: a directory on the runtime's machine.
- `gcs:<bucket>[/<prefix>]`: a Google Cloud Storage bucket, reached with the runtime's own Google identity, its Application Default Credentials. So there's no key to store.

Without it, the runtime serves no artifacts. `kindgi dev` keeps them in the pack's `.kindgi/dev/artifacts`. `KINDGI_ARTIFACT_MAX_BYTES` sets the upload cap. The startup output says where artifacts go, or that they're off.

## Next

- [Read a run's journal](https://docs.kindgi.com/v0.3.0/guides/runs/read-the-journal.md)
- [Show runs in your app](https://docs.kindgi.com/v0.3.0/guides/runs/show-runs-in-your-app.md)
- [Keep and purge deleted data](https://docs.kindgi.com/v0.3.0/deploy/retention.md)
