# Upload an artifact

POST

/v1/artifacts

```sh
curl --request POST \
  --url https://example.com/v1/artifacts \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: multipart/form-data' \
  --form file=@file \
  --form name=example \
  --form contentType=example \
  --form 'tags="example"' \
  --form ownerRunId=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0 \
  --form projectId=example \
  --form expectedHash=example
```

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`.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.1.10/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5).

## Request Bodyrequired

multipart/form-data

Multipart form. `file` is the byte stream (required). Other fields are optional metadata: `name` (defaults to file part filename), `contentType` (defaults to file part MIME), `tags` (JSON-encoded object), `ownerRunId` (RunId string), `expectedHash` (sha256 hex, framework rejects on mismatch).

object

**file**

required

string format: binary

**name**

string

**contentType**

string

**tags**

JSON-encoded `Record<string, string>` — parsed server-side.

string

**ownerRunId**

string format: uuid

**projectId**

The project it belongs to, when there’s no `ownerRunId` (with one, the run’s project, and this must agree). Default: the tenant’s default project.

string

**expectedHash**

Caller-computed sha256, hex-encoded, lowercase. Framework verifies + rejects on mismatch with `blob-hash-mismatch` (400).

string

/^\[0-9a-f]{64}$/

## Responses

### 201

Upload accepted; metadata returned.

application/json

object

**blobId**

required

ArtifactId — opaque branded string (a UUID).

string

**tenantId**

required

string format: uuid

**name**

required

Caller-supplied filename or logical name.

string

**contentType**

required

MIME type as declared by the uploader. Framework does NOT sniff content-type server-side.

string

**size**

required

Byte length of the persisted body.

integer

**hash**

required

Sha256 of the body, hex-encoded, lowercase (64 chars).

string

/^\[0-9a-f]{64}$/

**tags**

required

Free-form caller-supplied labels. Filterable on list via `?tag.<key>=<value>`.

object

**_key_**

additional properties

string

**ownerRunId**

Optional back-ref to the RunId that produced this blob.

string format: uuid

**projectId**

The project the artifact belongs to: its owner run’s, else the upload’s `projectId`, else the tenant’s default project. Reading it needs `read` there; deleting it, `write`. Absent on blobs stored before projects were recorded.

string

**createdBy**

Who uploaded it: `user:<id>` or `service_account:<id>`.

string

**createdAt**

required

string format: date-time

**bucket**

The bucket of the blob’s S3-compatible address (`/s3/{bucket}/{key}`). Present, with `key`, when the server’s storage gives the blob one: an object put through `/s3` has the bucket and key it was put under, and an upload through `POST /v1/artifacts` conventionally has bucket `artifacts` and its `blobId` as the key.

string

**key**

The key of the blob’s S3-compatible address (`/s3/{bucket}/{key}`). Present, with `bucket`, when the server’s storage gives the blob one.

string

##### Examplegenerated

```json
{
  "blobId": "example",
  "tenantId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
  "name": "example",
  "contentType": "example",
  "size": 1,
  "hash": "example",
  "tags": {
    "additionalProperty": "example"
  },
  "ownerRunId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
  "projectId": "example",
  "createdBy": "example",
  "createdAt": "2026-04-15T12:00:00Z",
  "bucket": "example",
  "key": "example"
}
```

### 400

Malformed multipart body, bad tag JSON, hash mismatch, declared size mismatch, or a `projectId` that isn’t the owner run’s.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

No `write` on the project (`permission-denied`), or the caller’s API key is limited to another project (`key-project-mismatch`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

No such owner run (`run-not-found`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

Idempotency-Key was reused with a different body, or resource-state conflict.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

Over the upload cap (`artifact-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/artifacts`: List artifact metadata](https://docs.kindgi.com/v0.1.10/reference/api/operations/artifactslist.md)
- [Kindgi 0.1.10](https://docs.kindgi.com/v0.1.10/releases/0.1.10.md)
- [Kindgi 0.1.8](https://docs.kindgi.com/v0.1.10/releases/0.1.8.md)
- [Error code `artifact-too-large`](https://docs.kindgi.com/v0.1.10/reference/errors.md#artifact-too-large)
- [Error code `key-project-mismatch`](https://docs.kindgi.com/v0.1.10/reference/errors.md#key-project-mismatch)
- [Error code `permission-denied`](https://docs.kindgi.com/v0.1.10/reference/errors.md#permission-denied)
- [Error code `run-not-found`](https://docs.kindgi.com/v0.1.10/reference/errors.md#run-not-found)
- [Every error code](https://docs.kindgi.com/v0.1.10/reference/errors.md)
