# Upload a file

Uploading is three calls: ask for a signed upload, send the bytes to storage, then add the file to the library. The file becomes an image, video or audio asset in the workspace, ready to place on a board or use as a reference.

## 1. Get a signed upload

Call `createSignedUploadUrl` with the file's `category`, `filename` and `contentType`, and your `workspaceId`. Select `uploadForm` to get a signed form.

```shell
curl https://api.plentylabs.com/graphql \
  -H "Authorization: Bearer $PLENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateSignedUpload($workspaceId: ID!) { createSignedUploadUrl(workspaceId: $workspaceId, category: UGC_IMAGE, filename: \"photo.png\", contentType: \"image/png\") { fileId uploadForm { url fields } } }",
    "variables": { "workspaceId": "YOUR_WORKSPACE_ID" }
  }' > response.json
```

| Category | Content types |
| --- | --- |
| `UGC_IMAGE` | `image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/avif`, `image/svg+xml` |
| `AI_IMAGE_REFERENCE` | `image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/avif` |
| `VIDEO` | `video/mp4`, `video/webm`, `video/quicktime`, `video/x-msvideo` |
| `AUDIO` | `audio/mpeg`, `audio/mp3`, `audio/wav`, `audio/x-wav`, `audio/wave`, `audio/ogg`, `audio/aac`, `audio/webm`, `audio/mp4`, `audio/x-m4a`, `audio/m4a`, `audio/flac`, `audio/x-flac` |

Any other content type returns `BAD_USER_INPUT` with the allowed list. `UploadCategory` in the schema lists every category.

The signed form and URL expire after 15 minutes.

## 2. Upload the file

POST a multipart form to `uploadForm.url`. Send every field in `uploadForm.fields`, then the file last, named `file`.

```shell
UPLOAD_URL=$(jq -r '.data.createSignedUploadUrl.uploadForm.url' response.json)
UPLOAD_FIELDS=$(jq -c '.data.createSignedUploadUrl.uploadForm.fields' response.json)

form=()
while IFS= read -r field; do form+=(-F "$field"); done < <(jq -r 'to_entries[] | "\(.key)=\(.value)"' <<< "$UPLOAD_FIELDS")

curl -f "$UPLOAD_URL" "${form[@]}" -F "file=@photo.png"
```

The form accepts a file from 1 byte to 256 MiB. Any 2xx status means the file is stored.

You can PUT the bytes to `signedUrl` instead. Send every header in `requiredHeaders`, exactly as given.

```shell
curl -f -X PUT "$SIGNED_URL" \
  -H "content-type: image/png" \
  -H "x-goog-if-generation-match: 0" \
  --data-binary @photo.png
```

## 3. Add it to the library

Pass the `fileId` to the mutation for its category. The file becomes an asset in the workspace library.

| Category | Mutation | Returns |
| --- | --- | --- |
| `UGC_IMAGE`, `AI_IMAGE_REFERENCE` | `uploadImage` | `Image` |
| `VIDEO` | `uploadVideo` | `Video` |
| `AUDIO` | `uploadAudio` | `Audio` |

```shell
curl https://api.plentylabs.com/graphql \
  -H "Authorization: Bearer $PLENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UploadImage($workspaceId: ID!, $fileId: ID!) { uploadImage(workspaceId: $workspaceId, input: { fileId: $fileId, displayName: \"Product photo\" }) { image { id status } } }",
    "variables": { "workspaceId": "YOUR_WORKSPACE_ID", "fileId": "FILE_ID" }
  }'
```

If the bytes did not arrive, the mutation is refused. Upload again, then retry with the same `fileId`.

For images and videos, width and height are measured from the file shortly after, when you omit them. Place the asset with [`PlaceCards`](https://app.plentylabs.com/api/playground?op=PlaceCards).

## Files outside the library

For any other category, call `confirmFileUpload` instead. It keeps the file without adding it to the library. Do not call it for a file you add with a step 3 mutation.

```shell
curl https://api.plentylabs.com/graphql \
  -H "Authorization: Bearer $PLENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation ConfirmFileUpload($fileId: ID!) { confirmFileUpload(fileId: $fileId) }",
    "variables": { "fileId": "FILE_ID" }
  }'
```

An upload that is neither added nor confirmed is deleted one to two hours after it was requested.

## Limits

- A key cannot import a file from a link. Upload the bytes.
