Skip to Content
Files API

Files API

The Files API allows you to upload and manage files for use with vision, audio, and other AI endpoints. It mirrors OpenAI’s Files API with PickLayer extensions.

Key Concepts

  • PickLayer File UUIDs: Uploads return a PickLayer file UUID (e.g., add71816-b93b-440d-9ff3-a5556b5dc39a). Use this UUID in AI calls, never provider IDs.
  • CDN & Signed URLs: Files are stored privately and served via CloudFront with per-request signed URLs.
  • Purpose-based uploads: Specify a purpose to categorize files.

Endpoints

MethodEndpointDescription
POST/v1/filesUpload a file
GET/v1/filesList files
GET/v1/files/{id}Get file metadata
GET/v1/files/{id}/contentDownload file content
DELETE/v1/files/{id}Delete a file

Upload a File

Multipart upload

curl -X POST "https://api.picklayer.dev/api/org/{org}/app/{app}/v1/files" \ -H "Authorization: Bearer $TOKEN" \ -F "purpose=vision" \ -F "file=@/tmp/cat.jpg;type=image/jpeg"

JSON base64 upload

curl -X POST "https://api.picklayer.dev/api/org/{org}/app/{app}/v1/files" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "purpose": "vision", "filename": "cat.jpg", "mime_type": "image/jpeg", "file_base64": "data:image/jpeg;base64,..." }'

Response

{ "id": "add71816-b93b-440d-9ff3-a5556b5dc39a", "purpose": "vision", "bytes": 245678, "filename": "cat.jpg", "mime_type": "image/jpeg", "status": "uploaded", "expires_at": "2025-02-15T12:00:00Z", "hash_sha256": "abc123..." }

Purposes

user_data is accepted as an alias for documents to stay OpenAI-SDK compatible. Responses for document uploads return purpose: "user_data".

PurposeDescription
visionImages for chat/vision endpoints
audioAudio files for transcription/speech
documentsPDFs, code, text files (alias: user_data)
assistantsTool assets
image_generationGenerated images from AI
video_generationGenerated videos from AI

Encrypted files

Files uploaded with is_encrypted: true (using the per-file file_key flow described in Encryption) work seamlessly with chat references — but the chat request must additionally carry a top-level file_keys map so the gateway can decrypt before forwarding bytes to the upstream provider.

{ "model": "openai/gpt-4o", "input": [ { "type": "input_text", "text": "Describe this image." }, { "type": "input_image", "image_url": { "url": "<encrypted-file-uuid>" } } ], "file_keys": { "<encrypted-file-uuid>": "<base64url-derived-key>" } }

The same file_keys mechanism applies to image_url, input_audio, and input_file references on Responses, Messages, and Chat Completions endpoints. The selection between the inline data: URL path and the signed CDN URL path (with opaque efk query param) is automatic. See Encryption — Attaching encrypted files to chat requests for the full architectural reference.

Using Files in AI Calls

In Chat Completions (vision)

{ "model": "openai/gpt-4o", "messages": [{ "role": "user", "content": [ { "type": "text", "text": "What do you see?" }, { "type": "image_url", "image_url": { "url": "add71816-b93b-440d-9ff3-a5556b5dc39a" } } ] }] }

In Responses API

{ "model": "openai/gpt-4o", "input": [ { "type": "input_text", "text": "What do you see?" }, { "type": "input_image", "image_url": { "url": "add71816-b93b-440d-9ff3-a5556b5dc39a" } } ] }

Audio input

{ "input_audio": { "url": "add71816-b93b-440d-9ff3-a5556b5dc39a", "format": "wav" } }

File Expiration

Files can be set to expire using the OpenAI-compatible expires_after parameter:

{ "purpose": "vision", "file_base64": "...", "expires_after": { "anchor": "created_at", "seconds": 86400 } }
  • Default expiration: 30 days
  • Use "seconds": "forever" for permanent storage

Limits

  • Max file size: 25 MB
  • Image parts per request: 10
  • Audio input duration: 15 minutes max

Sharing & access modes

Files have an access_mode that determines who can see them, plus an access_surface_mode that controls which surfaces (list, direct fetch, AI-only) expose them.

access_mode

ValueDescription
singleDefault. Only the uploader can read, patch, or delete the file.
sharedThe uploader grants access to specific users via shared_with. Each entry is { user_id, can_read, can_write }.
app-wideFile is visible to every user in the app. Non-owners are read-only; only owners/builders can patch or delete. App-wide writes require an active billing assignment.

shared_with shape

{ "access_mode": "shared", "shared_with": [ { "user_id": "...", "can_read": true, "can_write": false } ] }

access_surface_mode

ValueDescription
allDefault. File appears in list endpoints, is directly retrievable, and usable in AI calls.
directHidden from list for non-owners but retrievable by ID and usable in AI calls.
ai_onlyList/retrieve/signed-url/content routes all return 404 for non-owners; file is only reachable when referenced from AI endpoints (image_url, input_audio, etc.).

ACL endpoints

  • POST /v1/files/{file_id}/share — body { shared_with: [{ user_id, can_read, can_write }], access_surface_mode? }. Replaces or extends the ACL.
  • DELETE /v1/files/{file_id}/share/{user_id} — revokes a single user’s access.

SDK examples

# Python client.share_file( file_id, [{"user_id": other_user_id, "can_read": True, "can_write": False}], ) client.revoke_file_share(file_id, other_user_id)
// TypeScript / React await client.shareFile(fileId, [ { user_id: otherUserId, can_read: true, can_write: false }, ]); await client.revokeFileShare(fileId, otherUserId);
// Flutter await client.shareFile( fileId: fileId, sharedWith: [ {'user_id': otherUserId, 'can_read': true, 'can_write': false}, ], ); await client.revokeFileShare(fileId: fileId, userId: otherUserId);

App-wide files

App-wide files are visible to every user in the app. They have their own dedicated route family:

MethodPathDescription
GET/v1/files/app-wideList app-wide files.
POST/v1/files/app-wideUpload an app-wide file.
PATCH/v1/files/app-wide/{file_id}Update metadata (owner only).
DELETE/v1/files/app-wide/{file_id}Delete (owner only).
# Python — upload an app-wide file client.upload_file( purpose="documents", file_base64=b64, filename="policy.txt", mime_type="text/plain", access_mode="app-wide", ) client.list_app_wide_files()

Using POST /v1/files with access_mode: "app-wide" produces the same result as POST /v1/files/app-wide; both routes land on the same handler.

Builder File Upload (Programmatic)

Builders using the Management API with an API key (ExchangeSecretKey) can upload files programmatically using a three-step pre-signed URL flow. This avoids the REST multipart endpoint (which requires an interactive session token).

Step 1: Reserve a file ID

curl -s https://api.picklayer.ai/v1/graphql \ -H "Authorization: Bearer $BUILDER_TOKEN" \ -H "Content-Type: application/json" \ -H "x-hasura-role: builder" \ -d '{ "query": "mutation($input: BuilderFilesReserveInput!) { BuilderFilesReserve(input: $input) { code data { fileId uploadUrl uploadHeaders expiresAt } } }", "variables": { "input": { "filename": "training_data.csv", "purpose": "embeddings", "contentType": "text/csv" } } }'

Response includes a pre-signed S3 upload URL valid for 15 minutes.

Step 2: Upload to S3

curl -X PUT "$UPLOAD_URL" \ -H "Content-Type: text/csv" \ --data-binary @training_data.csv

Upload directly to S3 — no size proxy limits, no server buffering.

Step 3: Confirm the upload

curl -s https://api.picklayer.ai/v1/graphql \ -H "Authorization: Bearer $BUILDER_TOKEN" \ -H "Content-Type: application/json" \ -H "x-hasura-role: builder" \ -d '{ "query": "mutation($input: BuilderFilesConfirmInput!) { BuilderFilesConfirm(input: $input) { code data { fileId filename bytes mimeType createdAt purpose } } }", "variables": { "input": { "fileId": "YOUR_FILE_ID" } } }'

After confirmation, the file is available for use with BuilderEmbeddingsImportFiles and all other file operations.

Billing

  • Uploads meter file_upload_bytes
  • Downloads meter file_download_bytes
  • Vision billing uses image dimensions for accurate token estimation
Last updated on