# client.artifacts

> client.artifacts — the artifacts operations.

`client.artifacts` — the `artifacts` operations.

On `AsyncKindgi` every method is the same, awaited.

## `client.artifacts.list()`

```python signature
list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    owner_run_id: str | UUID | None = None,
    content_type: str | None = None,
    project_id: str | UUID | None = None,
    timeout: float | None = None,
) -> ArtifactCollectionPage
```

List artifact metadata. `GET /v1/artifacts`

Cursor-paginated. Metadata rows only (no bytes). Filters: `?ownerRunId=`, `?projectId=`, `?contentType=`, `?tag.<key>=<value>` (repeatable — every provided tag must match as AND). Sort order is binding-defined (typically `createdAt desc, blobId desc`). With authorization on, only artifacts in projects the caller can read are listed.

## `client.artifacts.upload()`

```python signature
upload(
    files: Mapping[str, Any],
    /,
    *,
    idempotency_key: str | None = None,
    data: Mapping[str, Any] | None = None,
    timeout: float | None = None,
) -> BlobMeta
```

Upload an artifact. `POST /v1/artifacts`

Multipart upload. `file` part carries the bytes; other form fields carry metadata (`name`, `contentType`, `tags` (JSON), `ownerRunId`, `projectId`, `expectedHash`). Framework computes sha256 and returns it in `BlobMeta.hash`. If `expectedHash` was supplied and diverges, response is `400 blob-hash-mismatch`. Content-type sniffing is NOT performed server-side — the framework trusts the caller. The artifact belongs to its owner run's project, else `projectId`, else the project the caller's API key is limited to, else the tenant's default project; uploading needs `write` there, and a key limited to a project uploads into that project only. An upload over the runtime's cap (default 100 MiB) is `413 artifact-too-large`.

## `client.artifacts.download()`

```python signature
download(blob_id: str | UUID, /, *, timeout: float | None = None) -> bytes
```

Download an artifact (stream). `GET /v1/artifacts/{blobId}`

Streams raw bytes. Response headers: `Content-Type` (as declared at upload), `Content-Length`, `X-Kindgi-Blob-Hash` (sha256 hex), `X-Kindgi-Blob-Name` (URI-encoded caller name).

## `client.artifacts.delete()`

```python signature
delete(
    blob_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> DeleteArtifactResult
```

Delete an artifact. `DELETE /v1/artifacts/{blobId}`

Idempotent: first call returns `{ deleted: true }`, subsequent calls return `{ deleted: false }` (already gone). The route responds 200 either way — 404 only when the id was never known (structurally impossible after `list` returned it).
