---
title: "Complete a presigned upload"
method: POST
path: "/files/{file_id}/complete"
tags: ["Files"]
---

# Complete a presigned upload

`POST /files/{file_id}/complete`

Finalize a presigned upload after you have PUT the bytes to the upload URL. Kicks off ingest/moderation and returns the file, which transitions to `ready` (or `failed`) asynchronously — poll GET /files/{file_id} to observe the terminal state.

## Path parameters

- `file_id` string, uuid, required

## Response `200`

Upload finalized; ingest started

- File — A file in the caller's namespace.
  - `id` string, uuid, required — File identifier, referenced as ImageRef.file_id.
  - `filename` string, nullable — Original filename supplied at upload, if any.
  - `mime_type` string, required — MIME type of the stored bytes (for example, image/jpeg).
  - `size_bytes` integer, required — Size of the stored object in bytes.
  - `purpose` 'input' | 'reference', required — How the file is intended to be used in a generation. `input` is the primary subject (e.g. the source image for an edit); `reference` is style/content guidance.
  - `state` 'pending' | 'ready' | 'failed' | 'deleted', required — Lifecycle state of an uploaded file. `pending` until bytes are received and the ingest pipeline runs; `ready` once it can be referenced from a generation; `failed` if ingest/moderation rejected it; `deleted` after a soft-delete.
  - `failure_reason` string, nullable — Human-readable reason when state is failed.
  - `expires_at` string, date-time, nullable — TTL set at upload, if any. After this time Luma may automatically delete the file and reclaim its bytes — you don't need to call DELETE yourself.
  - `created_at` string, date-time, required — Creation timestamp.
  - `deleted_at` string, date-time, nullable — Soft-delete timestamp, if the file was deleted.
  - `user_id` string, nullable — The opaque end-user tag supplied at upload, echoed back unchanged. Abuse-attribution only; not an access-control primitive.

## Other responses

- `401` — Missing or invalid API key
- `404` — File not found or belongs to another client
- `422` — The bytes have not been uploaded to S3 yet
- `503` — File ingest pipeline temporarily unavailable

---

[API](https://skmtc.net/lumalabs/apis/luma-agents-api.md) · [All operations](https://skmtc.net/lumalabs/apis/luma-agents-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/lumalabs/luma-agents-api/versions/2135bbde249c/schema)
