> ## Documentation Index
> Fetch the complete documentation index at: https://developer.fieldnode.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload

Attaching a file to a part is a three-step flow:

1. **Request a signed upload URL** from Fieldnode.
2. **Upload the file bytes** directly to the returned URL (blob storage).
3. **Attach the upload to the part** under a file group.

The bytes go straight to blob storage, not through the Fieldnode API. The API only mints the URL in step 1 and records the attachment in step 3.

## Prerequisites

* A PAT — see [Authentication](../authentication).
* The id of the part you want to attach the file to. You must be the IP owner (or have update access) of that part.

## Step 1 — Request a signed upload URL

Call `POST /v1/uploads` with the file name. Set `public: true` only if the file should be served without authentication (e.g. a preview image); otherwise leave it as `false`.

```bash theme={null}
curl https://api.lab.fieldnode.dev/v1/uploads \
    -X POST \
    -H 'Authorization: Bearer pat_your_personal_access_token' \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "drawing.pdf",
      "public": false
    }'
```

Response:

```json theme={null}
{
  "id": "01J8...",
  "method": "PUT",
  "signedUrl": "https://...blob.core.windows.net/...?sig=...",
  "expireTime": "2026-06-09T12:34:56Z",
  "headers": {
    "x-ms-blob-type": "BlockBlob"
  }
}
```

Keep the `id` — that is the **upload id** you will use in step 3. The `signedUrl` is single-use and expires at `expireTime`.

## Step 2 — Upload the file bytes

Send the raw file bytes to `signedUrl` using the `method` and `headers` from the response. Do **not** add your PAT — the signed URL carries its own authentication.

```bash theme={null}
curl --upload-file ./drawing.pdf \
    -X PUT \
    -H 'x-ms-blob-type: BlockBlob' \
    -H 'Content-Type: application/pdf' \
    'https://...blob.core.windows.net/...?sig=...'
```

A successful upload returns `201 Created` (or `200 OK`) with an empty body. If the URL has expired, restart from step 1.

## Step 3 — Attach the upload to the part

Call `PATCH /v1/parts/{id}/files` with the upload id and the **file group** to attach it under:

```bash theme={null}
curl https://api.lab.fieldnode.dev/v1/parts/{part_id}/files \
    -X PATCH \
    -H 'Authorization: Bearer pat_your_personal_access_token' \
    -H 'Content-Type: application/json' \
    -d '{
      "add": [
        { "group": "document", "uploadId": "01J8..." }
      ]
    }'
```

A successful response has status `204 No Content`.

### File groups

| Group             | Use for                                            |
| ----------------- | -------------------------------------------------- |
| `preview`         | Thumbnail or preview image shown in part listings. |
| `document`        | General attachments (drawings, datasheets, PDFs).  |
| `ip`              | IP-restricted files only visible to the IP owner.  |
| `partDevelopment` | Development files attached during part design.     |
| `development`     | Additional development assets.                     |

## Verify the attachment

List the part's files to confirm:

```bash theme={null}
curl https://api.lab.fieldnode.dev/v1/parts/{part_id}/files \
    -H 'Authorization: Bearer pat_your_personal_access_token'
```

Each item includes `id`, `name`, `group`, `url`, and (for non-public files) a short-lived `signedUrl` / `signedDownloadUrl` you can use to fetch the bytes back.

## Removing files

The same endpoint removes files — pass their **file ids** (not upload ids) under `remove`:

```bash theme={null}
curl https://api.lab.fieldnode.dev/v1/parts/{part_id}/files \
    -X PATCH \
    -H 'Authorization: Bearer pat_your_personal_access_token' \
    -H 'Content-Type: application/json' \
    -d '{
      "remove": ["01J9..."]
    }'
```

`add` and `remove` can be combined in a single request.
