> For the complete documentation index, see [llms.txt](https://docs.adaptria.locaria.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.adaptria.locaria.com/internal-api/reference/file-upload.md).

# 📤 File Upload

### File Upload

Upload files to attach to your RFQ services. Files must be uploaded **before** creating your RFQ.

#### Complete File Upload Workflow

```
Step 1: Request Presigned URL
POST /api/files/upload/presigned-url
{
  "filename": "document.pdf",
  "sizeBytes": 1024000,
  "mimeType": "application/pdf",
  "entityType": "rfq"
}
→ Response: { fileId: "67eb9e88-...", presignedUrl: "...", ... }

Step 2: Upload File to Presigned URL
PUT <presignedUrl>
Content-Type: <uploadInstructions.contentType>
[File binary data]
→ Response: 200 OK

Step 3: Use fileId in RFQ
POST /api/public/v1/rfqs
{
  "services": [{
    "serviceId": "...",
    "type": "NON_LINGUISTICS",
    "targetMarketIds": ["..."],
    "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
  }]
}
```

#### When to Upload Files

Upload files **before** creating your RFQ if you want to attach them to services. The `fileId` you receive can be used in the `assetFileIds` array when creating RFQs.

#### File Requirements

* **Max Size**: 100MB (104,857,600 bytes)
* **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP
* **Entity Type**: Use `"rfq"` for RFQ-related files

#### Using File IDs in RFQs

Once you have a `fileId` from the upload endpoint, include it in your service's `assetFileIds` array:

```json
{
  "serviceId": "05b68c28-5659-4c57-809b-a55bae97f967",
  "type": "NON_LINGUISTICS",
  "targetMarketIds": ["976fba00-3e0d-40fe-b766-6017cb44c3a7"],
  "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
}
```

**Note**: The `fileId` from the upload endpoint goes directly into the `assetFileIds` array. The system automatically transforms it to the internal `assets` format.

Get file upload endpoint details below.

## 📤 Upload File and Get File ID

> \## Upload Files for RFQ Service Assets\
> \
> This endpoint allows you to upload files directly via \`multipart/form-data\` that will be attached to specific services in your RFQ.\
> \
> \### How It Works\
> \
> \*\*Direct File Upload (Multipart Only)\*\*\
> \- Use \`multipart/form-data\` content type\
> \- Select a file directly in Swagger UI or send as multipart form\
> \- The system automatically:\
> &#x20; 1\. Generates a presigned URL\
> &#x20; 2\. Uploads the file to GCP storage\
> &#x20; 3\. Saves file metadata\
> &#x20; 4\. Returns only the \`fileId\` (ready to use in RFQ services)\
> \- \*\*Response\*\*: \`{ success: true, message: "File uploaded successfully", data: { fileId: "..." } }\`\
> \- \*\*All in one request!\*\* No need to make a separate PUT request\
> \
> \### Complete File Upload Workflow\
> \
> \*\*Important\*\*: Files are uploaded \*\*per service\*\*. Each service in your RFQ can have its own set of files.\
> \
> \`\`\`\
> Step 1: Upload File for a Specific Service\
> POST /api/public/v1/files/upload/presigned-url\
> Content-Type: multipart/form-data\
> \- file: \[binary file data]\
> \
> → Response: { success: true, data: { fileId: "91cc2de4-293b-4c37-b257-5d39cd1133da" } }\
> \
> Step 2: Upload Another File for a Different Service (if needed)\
> POST /api/public/v1/files/upload/presigned-url\
> Content-Type: multipart/form-data\
> \- file: \[another binary file data]\
> \
> → Response: { success: true, data: { fileId: "dc68867c-beb5-46e3-b291-47978c94783b" } }\
> \
> Step 3: Create or Update RFQ with fileIds in Service Objects\
> POST /api/public/v1/rfqs\
> {\
> &#x20; "companyId": "...",\
> &#x20; "projectName": "My Project",\
> &#x20; "services": \[\
> &#x20;   {\
> &#x20;     "serviceId": "33cecd1e-6629-41d8-bc65-f1ad832a2bd2",\
> &#x20;     "type": "NON\_LINGUISTICS",\
> &#x20;     "targetMarketIds": \["03b91040-5931-4c24-9310-7a33861edde2"],\
> &#x20;     "assetFileIds": \["91cc2de4-293b-4c37-b257-5d39cd1133da"]  ← File for this service\
> &#x20;   },\
> &#x20;   {\
> &#x20;     "serviceId": "7ff59ce3-d3e7-4898-ac35-63b38531c17d",\
> &#x20;     "type": "NON\_LINGUISTICS",\
> &#x20;     "targetMarketIds": \["55247866-be7d-4dad-8cea-5e3a71a8e2a6"],\
> &#x20;     "assetFileIds": \["dc68867c-beb5-46e3-b291-47978c94783b"]  ← Different file for this service\
> &#x20;   }\
> &#x20; ]\
> }\
> \
> OR Update an existing RFQ:\
> PATCH /api/public/v1/rfqs/{rfqId}\
> {\
> &#x20; "services": \[\
> &#x20;   {\
> &#x20;     "id": "00000000-0000-0000-0000-000000000001",\
> &#x20;     "type": "NON\_LINGUISTICS",\
> &#x20;     "serviceUUID": "33cecd1e-6629-41d8-bc65-f1ad832a2bd2",\
> &#x20;     "targetMarkets": \[{"targetMarketId": "03b91040-5931-4c24-9310-7a33861edde2"}],\
> &#x20;     "assets": \[\
> &#x20;       {\
> &#x20;         "type": "FILE",\
> &#x20;         "assetType": "SOURCE",\
> &#x20;         "fileId": "91cc2de4-293b-4c37-b257-5d39cd1133da"\
> &#x20;       }\
> &#x20;     ]\
> &#x20;   }\
> &#x20; ]\
> }\
> \`\`\`\
> \
> \### File Requirements\
> \
> \- \*\*Max Size\*\*: 100MB (104,857,600 bytes)\
> \- \*\*Allowed Types\*\*: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP\
> \
> \### Using File IDs in RFQ Services\
> \
> \*\*For Creating RFQs\*\*: Include \`fileId\`s in the \`assetFileIds\` array of each service object:\
> \
> \`\`\`json\
> {\
> &#x20; "serviceId": "05b68c28-5659-4c57-809b-a55bae97f967",\
> &#x20; "type": "NON\_LINGUISTICS",\
> &#x20; "targetMarketIds": \["976fba00-3e0d-40fe-b766-6017cb44c3a7"],\
> &#x20; "assetFileIds": \["67eb9e88-7962-41a1-9695-f2051a968728", "another-file-id-here"]\
> }\
> \`\`\`\
> \
> \*\*For Updating RFQs\*\*: Use the internal \`assets\` format with \`fileId\`:\
> \
> \`\`\`json\
> {\
> &#x20; "assets": \[\
> &#x20;   {\
> &#x20;     "type": "FILE",\
> &#x20;     "assetType": "SOURCE",\
> &#x20;     "fileId": "67eb9e88-7962-41a1-9695-f2051a968728"\
> &#x20;   }\
> &#x20; ]\
> }\
> \`\`\`\
> \
> \*\*Note\*\*:\
> \- Each service can have multiple files (multiple \`fileId\`s in the \`assetFileIds\` array)\
> \- Files are associated with specific services, not the entire RFQ\
> \- The system automatically transforms \`assetFileIds\` to the internal \`assets\` format when creating RFQs\
> \- See the \[RFQs]\(#tag/%F0%9F%93%84%20RFQs) section for complete RFQ creation examples.

````json
{"openapi":"3.1.0","info":{"title":"Adaptria Public API: Connector","version":"1.0.0"},"tags":[{"name":"📤 File Upload","description":"## File Upload\n\nUpload files to attach to your RFQ services. Files must be uploaded **before** creating your RFQ.\n\n### Complete File Upload Workflow\n\n```\nStep 1: Request Presigned URL\nPOST /api/files/upload/presigned-url\n{\n  \"filename\": \"document.pdf\",\n  \"sizeBytes\": 1024000,\n  \"mimeType\": \"application/pdf\",\n  \"entityType\": \"rfq\"\n}\n→ Response: { fileId: \"67eb9e88-...\", presignedUrl: \"...\", ... }\n\nStep 2: Upload File to Presigned URL\nPUT <presignedUrl>\nContent-Type: <uploadInstructions.contentType>\n[File binary data]\n→ Response: 200 OK\n\nStep 3: Use fileId in RFQ\nPOST /api/public/v1/rfqs\n{\n  \"services\": [{\n    \"serviceId\": \"...\",\n    \"type\": \"NON_LINGUISTICS\",\n    \"targetMarketIds\": [\"...\"],\n    \"assetFileIds\": [\"67eb9e88-7962-41a1-9695-f2051a968728\"]\n  }]\n}\n```\n\n### When to Upload Files\n\nUpload files **before** creating your RFQ if you want to attach them to services. The `fileId` you receive can be used in the `assetFileIds` array when creating RFQs.\n\n### File Requirements\n\n- **Max Size**: 100MB (104,857,600 bytes)\n- **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP\n- **Entity Type**: Use `\"rfq\"` for RFQ-related files\n\n### Using File IDs in RFQs\n\nOnce you have a `fileId` from the upload endpoint, include it in your service's `assetFileIds` array:\n\n```json\n{\n  \"serviceId\": \"05b68c28-5659-4c57-809b-a55bae97f967\",\n  \"type\": \"NON_LINGUISTICS\",\n  \"targetMarketIds\": [\"976fba00-3e0d-40fe-b766-6017cb44c3a7\"],\n  \"assetFileIds\": [\"67eb9e88-7962-41a1-9695-f2051a968728\"]\n}\n```\n\n**Note**: The `fileId` from the upload endpoint goes directly into the `assetFileIds` array. The system automatically transforms it to the internal `assets` format.\n\nGet file upload endpoint details below.\n"}],"servers":[{"url":"https://api.adaptria.locaria.com","description":"Production Environment"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"Api-key","description":"## API Key Authentication\n\nStatic API key obtained from the Adaptria Portal or via `POST /api/credentials/api-keys`.\n\n**How to authenticate**:\n1. Generate an API key from the Adaptria Portal (Settings → API Keys) or ask your admin\n2. Include the key in all requests using the `Api-key` header\n\n**Key format**: `lla_live_<32-character-string>`\n\n**No token exchange needed** — the key is used directly in every request. Revoke or rotate keys from the portal."}},"schemas":{"DirectFileUploadResponse":{"type":"object","properties":{"fileId":{"type":"string","format":"uuid","description":"**File ID - Use this in your RFQ assets array!**"}},"required":["fileId"]}}},"paths":{"/api/public/v1/files/upload/presigned-url":{"post":{"summary":"📤 Upload File and Get File ID","description":"## Upload Files for RFQ Service Assets\n\nThis endpoint allows you to upload files directly via `multipart/form-data` that will be attached to specific services in your RFQ.\n\n### How It Works\n\n**Direct File Upload (Multipart Only)**\n- Use `multipart/form-data` content type\n- Select a file directly in Swagger UI or send as multipart form\n- The system automatically:\n  1. Generates a presigned URL\n  2. Uploads the file to GCP storage\n  3. Saves file metadata\n  4. Returns only the `fileId` (ready to use in RFQ services)\n- **Response**: `{ success: true, message: \"File uploaded successfully\", data: { fileId: \"...\" } }`\n- **All in one request!** No need to make a separate PUT request\n\n### Complete File Upload Workflow\n\n**Important**: Files are uploaded **per service**. Each service in your RFQ can have its own set of files.\n\n```\nStep 1: Upload File for a Specific Service\nPOST /api/public/v1/files/upload/presigned-url\nContent-Type: multipart/form-data\n- file: [binary file data]\n\n→ Response: { success: true, data: { fileId: \"91cc2de4-293b-4c37-b257-5d39cd1133da\" } }\n\nStep 2: Upload Another File for a Different Service (if needed)\nPOST /api/public/v1/files/upload/presigned-url\nContent-Type: multipart/form-data\n- file: [another binary file data]\n\n→ Response: { success: true, data: { fileId: \"dc68867c-beb5-46e3-b291-47978c94783b\" } }\n\nStep 3: Create or Update RFQ with fileIds in Service Objects\nPOST /api/public/v1/rfqs\n{\n  \"companyId\": \"...\",\n  \"projectName\": \"My Project\",\n  \"services\": [\n    {\n      \"serviceId\": \"33cecd1e-6629-41d8-bc65-f1ad832a2bd2\",\n      \"type\": \"NON_LINGUISTICS\",\n      \"targetMarketIds\": [\"03b91040-5931-4c24-9310-7a33861edde2\"],\n      \"assetFileIds\": [\"91cc2de4-293b-4c37-b257-5d39cd1133da\"]  ← File for this service\n    },\n    {\n      \"serviceId\": \"7ff59ce3-d3e7-4898-ac35-63b38531c17d\",\n      \"type\": \"NON_LINGUISTICS\",\n      \"targetMarketIds\": [\"55247866-be7d-4dad-8cea-5e3a71a8e2a6\"],\n      \"assetFileIds\": [\"dc68867c-beb5-46e3-b291-47978c94783b\"]  ← Different file for this service\n    }\n  ]\n}\n\nOR Update an existing RFQ:\nPATCH /api/public/v1/rfqs/{rfqId}\n{\n  \"services\": [\n    {\n      \"id\": \"00000000-0000-0000-0000-000000000001\",\n      \"type\": \"NON_LINGUISTICS\",\n      \"serviceUUID\": \"33cecd1e-6629-41d8-bc65-f1ad832a2bd2\",\n      \"targetMarkets\": [{\"targetMarketId\": \"03b91040-5931-4c24-9310-7a33861edde2\"}],\n      \"assets\": [\n        {\n          \"type\": \"FILE\",\n          \"assetType\": \"SOURCE\",\n          \"fileId\": \"91cc2de4-293b-4c37-b257-5d39cd1133da\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### File Requirements\n\n- **Max Size**: 100MB (104,857,600 bytes)\n- **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP\n\n### Using File IDs in RFQ Services\n\n**For Creating RFQs**: Include `fileId`s in the `assetFileIds` array of each service object:\n\n```json\n{\n  \"serviceId\": \"05b68c28-5659-4c57-809b-a55bae97f967\",\n  \"type\": \"NON_LINGUISTICS\",\n  \"targetMarketIds\": [\"976fba00-3e0d-40fe-b766-6017cb44c3a7\"],\n  \"assetFileIds\": [\"67eb9e88-7962-41a1-9695-f2051a968728\", \"another-file-id-here\"]\n}\n```\n\n**For Updating RFQs**: Use the internal `assets` format with `fileId`:\n\n```json\n{\n  \"assets\": [\n    {\n      \"type\": \"FILE\",\n      \"assetType\": \"SOURCE\",\n      \"fileId\": \"67eb9e88-7962-41a1-9695-f2051a968728\"\n    }\n  ]\n}\n```\n\n**Note**:\n- Each service can have multiple files (multiple `fileId`s in the `assetFileIds` array)\n- Files are associated with specific services, not the entire RFQ\n- The system automatically transforms `assetFileIds` to the internal `assets` format when creating RFQs\n- See the [RFQs](#tag/%F0%9F%93%84%20RFQs) section for complete RFQ creation examples.","tags":["📤 File Upload"],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload (binary). Max size: 100MB. Supported types: Images, Documents, Videos, Audio, Archives, Subtitles, Translation files, and more."}}},"encoding":{"file":{"contentType":"image/jpeg,image/png,image/webp,image/gif,image/svg+xml,image/bmp,image/tiff,image/heic,image/heif,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain,application/rtf,application/vnd.oasis.opendocument.text,application/vnd.oasis.opendocument.spreadsheet,application/vnd.oasis.opendocument.presentation,text/csv,video/mp4,video/webm,video/quicktime,video/x-msvideo,video/x-matroska,video/x-m4v,video/3gpp,audio/mpeg,audio/wav,audio/ogg,audio/mp4,audio/aac,audio/flac,audio/opus,application/zip,application/x-zip-compressed,application/x-7z-compressed,application/x-rar-compressed,application/x-tar,application/gzip,application/x-bzip2,application/x-xz,text/vtt,text/srt,text/x-ass,text/x-ssa,text/x-sbv,application/x-subtitle,text/x-cap,application/x-tr,application/xml,application/xliff+xml,application/json,text/markdown,application/x-epub+zip,application/x-mobipocket-ebook,application/x-scorm,application/x-imscc+xml,application/x-h5p+zip,application/x-dita+xml,application/x-ditamap+xml,application/x-gettext,application/x-gettext-translation,application/x-gettext-translation-mo,application/x-tmx+xml,text/x-yaml,application/x-yaml,application/x-plist,application/x-resx+xml,application/x-arb,application/x-stl,application/mxf,application/x-bxf,text/x-edl,application/x-fcpxml,application/x-otio,text/asciidoc,application/x-tex,text/x-rst,application/xml-dtd"}}}}},"responses":{"201":{"description":"File uploaded successfully. The file is uploaded internally, metadata is saved automatically, and only fileId is returned (ready to use in RFQs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectFileUploadResponse"}}}},"400":{"description":"Invalid request (invalid file type, size too large, etc.)"},"401":{"description":"Authentication required"},"403":{"description":"Access denied"}}}}}}
````

## 🔗 Generate Presigned Upload URL

> \## Generate a presigned URL for direct upload\
> \
> Use this when you want to PUT the file bytes to GCP storage yourself, rather\
> than streaming them through this API (e.g. the file is already accessible to\
> an agent or another service).\
> \
> \*\*Access\*\*: restricted to Locaria internal staff roles (admin, locaria\_user, vendor\_manager, finance\_ops). Client API tokens will receive 403.\
> \
> \### Flow\
> 1\. \`POST /api/public/v1/files/upload/generate-url\` with file metadata → receive \`{ fileId, presignedUrl, expiresAt, uploadInstructions }\`\
> 2\. \`PUT \<presignedUrl>\` with the file bytes and the \`Content-Type\` header from \`uploadInstructions.contentType\`\
> 3\. Use the returned \`fileId\` in your RFQ \`assetFileIds\` array (see the \[RFQs]\(#tag/%F0%9F%93%84%20RFQs) section)\
> \
> \### Notes\
> \- The presigned URL expires in \~15 minutes — use it promptly\
> \- \`maxSizeBytes\` in \`uploadInstructions\` is the upper bound the URL was signed for; uploads exceeding it will be rejected by storage

````json
{"openapi":"3.1.0","info":{"title":"Adaptria Public API: Connector","version":"1.0.0"},"tags":[{"name":"📤 File Upload","description":"## File Upload\n\nUpload files to attach to your RFQ services. Files must be uploaded **before** creating your RFQ.\n\n### Complete File Upload Workflow\n\n```\nStep 1: Request Presigned URL\nPOST /api/files/upload/presigned-url\n{\n  \"filename\": \"document.pdf\",\n  \"sizeBytes\": 1024000,\n  \"mimeType\": \"application/pdf\",\n  \"entityType\": \"rfq\"\n}\n→ Response: { fileId: \"67eb9e88-...\", presignedUrl: \"...\", ... }\n\nStep 2: Upload File to Presigned URL\nPUT <presignedUrl>\nContent-Type: <uploadInstructions.contentType>\n[File binary data]\n→ Response: 200 OK\n\nStep 3: Use fileId in RFQ\nPOST /api/public/v1/rfqs\n{\n  \"services\": [{\n    \"serviceId\": \"...\",\n    \"type\": \"NON_LINGUISTICS\",\n    \"targetMarketIds\": [\"...\"],\n    \"assetFileIds\": [\"67eb9e88-7962-41a1-9695-f2051a968728\"]\n  }]\n}\n```\n\n### When to Upload Files\n\nUpload files **before** creating your RFQ if you want to attach them to services. The `fileId` you receive can be used in the `assetFileIds` array when creating RFQs.\n\n### File Requirements\n\n- **Max Size**: 100MB (104,857,600 bytes)\n- **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP\n- **Entity Type**: Use `\"rfq\"` for RFQ-related files\n\n### Using File IDs in RFQs\n\nOnce you have a `fileId` from the upload endpoint, include it in your service's `assetFileIds` array:\n\n```json\n{\n  \"serviceId\": \"05b68c28-5659-4c57-809b-a55bae97f967\",\n  \"type\": \"NON_LINGUISTICS\",\n  \"targetMarketIds\": [\"976fba00-3e0d-40fe-b766-6017cb44c3a7\"],\n  \"assetFileIds\": [\"67eb9e88-7962-41a1-9695-f2051a968728\"]\n}\n```\n\n**Note**: The `fileId` from the upload endpoint goes directly into the `assetFileIds` array. The system automatically transforms it to the internal `assets` format.\n\nGet file upload endpoint details below.\n"}],"servers":[{"url":"https://api.adaptria.locaria.com","description":"Production Environment"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"Api-key","description":"## API Key Authentication\n\nStatic API key obtained from the Adaptria Portal or via `POST /api/credentials/api-keys`.\n\n**How to authenticate**:\n1. Generate an API key from the Adaptria Portal (Settings → API Keys) or ask your admin\n2. Include the key in all requests using the `Api-key` header\n\n**Key format**: `lla_live_<32-character-string>`\n\n**No token exchange needed** — the key is used directly in every request. Revoke or rotate keys from the portal."}},"schemas":{"GeneratePresignedUrlRequest":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Original filename (max 255 characters)"},"sizeBytes":{"type":"integer","exclusiveMinimum":0,"maximum":104857600,"description":"File size in bytes (max 100MB = 104857600 bytes)"},"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/gif","image/svg+xml","image/bmp","image/tiff","image/heic","image/heif","application/pdf","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application/vnd.ms-powerpoint","application/vnd.openxmlformats-officedocument.presentationml.presentation","text/plain","application/rtf","application/vnd.oasis.opendocument.text","application/vnd.oasis.opendocument.spreadsheet","application/vnd.oasis.opendocument.presentation","text/csv","video/mp4","video/webm","video/quicktime","video/x-msvideo","video/x-matroska","video/x-m4v","video/3gpp","audio/mpeg","audio/wav","audio/ogg","audio/mp4","audio/aac","audio/flac","audio/opus","application/zip","application/x-zip-compressed","application/x-7z-compressed","application/x-rar-compressed","application/x-tar","application/gzip","application/x-bzip2","application/x-xz","text/vtt","text/srt","text/x-ass","text/x-ssa","text/x-sbv","application/x-subtitle","text/x-cap","application/x-tr","application/xml","application/xliff+xml","application/json","text/markdown","application/x-epub+zip","application/x-mobipocket-ebook","application/x-scorm","application/x-imscc+xml","application/x-h5p+zip","application/x-dita+xml","application/x-ditamap+xml","application/x-gettext","application/x-gettext-translation","application/x-gettext-translation-mo","application/x-tmx+xml","text/x-yaml","application/x-yaml","application/x-plist","application/x-resx+xml","application/x-arb","application/x-stl","application/mxf","application/x-bxf","text/x-edl","application/x-fcpxml","application/x-otio","text/asciidoc","application/x-tex","text/x-rst","application/xml-dtd"],"description":"File MIME type. Must be one of the allowed types."},"entityType":{"type":"string","enum":["user","project","rfq","quote","job","profile"],"description":"Type of entity the file belongs to. Use \"rfq\" for RFQ-related files."},"entityId":{"type":"string","format":"uuid","description":"ID of the entity (optional). For RFQs, you can omit this or use the RFQ ID if you have it."},"isPublic":{"type":"boolean","default":false,"description":"Whether the file should be publicly accessible. Default: false (private)"}},"required":["filename","sizeBytes","mimeType","entityType"]},"GeneratePresignedUrlResponse":{"type":"object","properties":{"fileId":{"type":"string","format":"uuid","description":"**File ID - Use this in your RFQ assets array!**"},"presignedUrl":{"type":"string","format":"uri","description":"Presigned URL for uploading the file directly to cloud storage"},"expiresAt":{"type":"string","description":"ISO timestamp when the presigned URL expires (typically 15 minutes)"},"uploadInstructions":{"type":"object","properties":{"method":{"type":"string","enum":["PUT"],"description":"HTTP method to use for upload"},"contentType":{"type":"string","description":"Content-Type header to use when uploading"},"maxSizeBytes":{"type":"number","description":"Maximum allowed file size in bytes"}},"required":["method","contentType","maxSizeBytes"],"description":"Instructions for uploading the file to the presigned URL"},"downloadUrl":{"type":"string","format":"uri","description":"Temporary download URL (for testing)"},"downloadUrlExpiresAt":{"type":"string","description":"When the download URL expires"}},"required":["fileId","presignedUrl","expiresAt","uploadInstructions"]}}},"paths":{"/api/public/v1/files/upload/generate-url":{"post":{"summary":"🔗 Generate Presigned Upload URL","description":"## Generate a presigned URL for direct upload\n\nUse this when you want to PUT the file bytes to GCP storage yourself, rather\nthan streaming them through this API (e.g. the file is already accessible to\nan agent or another service).\n\n**Access**: restricted to Locaria internal staff roles (admin, locaria_user, vendor_manager, finance_ops). Client API tokens will receive 403.\n\n### Flow\n1. `POST /api/public/v1/files/upload/generate-url` with file metadata → receive `{ fileId, presignedUrl, expiresAt, uploadInstructions }`\n2. `PUT <presignedUrl>` with the file bytes and the `Content-Type` header from `uploadInstructions.contentType`\n3. Use the returned `fileId` in your RFQ `assetFileIds` array (see the [RFQs](#tag/%F0%9F%93%84%20RFQs) section)\n\n### Notes\n- The presigned URL expires in ~15 minutes — use it promptly\n- `maxSizeBytes` in `uploadInstructions` is the upper bound the URL was signed for; uploads exceeding it will be rejected by storage","tags":["📤 File Upload"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePresignedUrlRequest"}}}},"responses":{"201":{"description":"Presigned URL generated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePresignedUrlResponse"}}}},"400":{"description":"Invalid request (invalid file type, size too large, etc.)"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — admin, locaria_user, vendor_manager, or finance_ops role required (Locaria internal staff only)"}}}}}}
````
