Upload an asset
Upload a local image for a Technique input with FLORA MCP, a signed upload reservation, local shell curl, and asset completion.
import { Steps, Step, Callout } from ‘@stainless-api/docs/components’;
To run a Technique against a user-supplied image (or video), upload it to FLORA first and pass the resulting URL as an input. For coding agents uploading a local image, use FLORA MCP to reserve a signed upload, upload the file from the local shell with curl, then complete the asset through FLORA MCP. Do not base64-encode image bytes into MCP tool calls.
What you’ll build: a flow that takes a local file path or hosted file URL, uploads it safely, and returns a permanent FLORA URL ready to use as a Technique input.
If the agent cannot access the local file or its shell cannot reach the upload host, that is a coding-agent environment limitation, not a FLORA limitation. In that case, send the user to the relevant FLORA project so they can upload the asset directly in the FLORA app. If no project exists yet but a workspace is known, create a new project first and send the user to https://app.flora.ai/projects/{project_id}.
Upload a local image from a coding agent with MCP and curl
Section titled “Upload a local image from a coding agent with MCP and curl”If the image exists as a local file and your environment can read the file and reach the returned upload host, use the MCP + local shell flow.
MCP + local shell upload flow
Section titled “MCP + local shell upload flow”Choose the right upload path
Section titled “Choose the right upload path”If the image already has a hosted HTTPS URL, use that URL directly with client.assets.create. This is the simplest path for attachments exposed as URLs by an AI client or files already hosted on an allowlisted source.
const asset = await client.assets.create({ source: 'https://uploads.anthropic.com/example-image.png', workspace_id: 'ws_XXXX',});
await client.projects.assets.attachAsset(asset.asset_id, { projectId: 'prj_XXXX',});If the image only exists as a local file, use the MCP + local shell upload flow above.
TypeScript with a hosted file URL
Section titled “TypeScript with a hosted file URL”If your environment exposes the uploaded file as an HTTPS URL, do not download and re-upload the bytes. Create the FLORA asset from the URL and attach it to the project:
import Flora from '@flora-ai/flora';
const client = new Flora({ apiKey: process.env.FLORA_API_KEY });
const asset = await client.assets.create({ source: 'https://uploads.anthropic.com/example-image.png', workspace_id: 'ws_XXXX',});
await client.projects.assets.attachAsset(asset.asset_id, { projectId: 'prj_XXXX',});
const run = await client.techniques.runs.create('portrait-enhancer', { inputs: [ { id: 'input_image', type: 'imageUrl', value: asset.url }, ], mode: 'async',});If your environment only has an inaccessible chat attachment and no usable file URL, do not use TypeScript or FLORA MCP to upload bytes. Ask the user to upload the file directly in the FLORA app instead.
Agent flow for a local file
Section titled “Agent flow for a local file”When the user asks an agent to run a Technique on an attached or local image, the agent should follow this flow:
- Confirm the Technique input ID with
search_docsor by retrieving the Technique. - Confirm the file exists locally and detect the content type.
- Reserve the upload through FLORA MCP:
const asset = await client.assets.create({ source: 'signed-url', workspace_id: 'ws_XXXX', file_name: 'photo.png', content_type: 'image/png',});console.log(JSON.stringify(asset));- Upload the local file with shell
curlusing the upload URL and form fields returned in the previous step. Keep the command mechanical: copy the returned fields into-Fform parts, include the file field, and use progress output for larger files.
# Build this command from the reservation response:# - POST URL: asset.upload.url# - form fields: every entry in asset.upload.form_fields# - file field: asset.upload.file_field, usually "file"curl -# -X POST "$UPLOAD_URL" \ -F "<returned field name>=<returned field value>" \ -F "<returned field name>=<returned field value>" \ -F "$FILE_FIELD=@/path/to/photo.png" \ -w "\nHTTP %{http_code} | uploaded %{size_upload}B in %{time_total}s\n"The agent should copy every returned form field exactly from the reservation response. Do not ask the user for separate upload credentials, and do not explain or expose individual credential-like fields beyond using them mechanically in the local curl command.
- Complete the asset through FLORA MCP:
const completed = await client.assets.complete(asset.asset_id);console.log(completed.url);- Use
completed.urlas the TechniqueimageUrlinput, start the run, and poll until completion. If the run exceeds one execute call’s timeout, poll in shorter windows and report progress between calls.
Do not base64-encode the file into MCP calls. Do not ask for ImageKit credentials. The upload reservation already contains the temporary upload instructions needed by curl.
Attach the asset to a Project
Section titled “Attach the asset to a Project”So uploaded assets show up in the FLORA canvas alongside generated work:
await client.projects.assets.attachAsset(asset.asset_id, { projectId: 'prj_XXXX',});When a coding agent cannot upload the file
Section titled “When a coding agent cannot upload the file”Some coding-agent environments expose an uploaded image in chat but do not expose the underlying file bytes to tools, or they block outbound requests to the returned upload host. In that case, the agent should stop trying to upload the bytes itself and guide the user into FLORA. The user should upload the file directly in the FLORA app, not through FLORA MCP.
Use this fallback:
- Explain that the limitation is the coding-agent environment, not FLORA.
- Do not base64-encode the image or pass file bytes through a tool call.
- Do not try to upload the bytes with FLORA MCP.
- Do not ask the user to change network allowlists.
- If a target
project_idis known, send the user tohttps://app.flora.ai/projects/{project_id}and ask them to upload the image directly in FLORA. - If no target project exists but a
workspace_idis known, create a new project and send the user to that project URL:
const project = await client.projects.create({ name: 'Uploaded image', workspace_id: 'ws_XXXX',});
console.log(`https://app.flora.ai/projects/${project.project_id}`);If there are multiple possible workspaces and none is implied by the user’s request, ask which workspace to use before creating a project. After the project exists, the upload itself should still happen directly in the FLORA app.
A good user-facing response is:
I can’t access the uploaded image from this coding-agent environment. This is a constraint of the agent’s environment, not FLORA itself. Please open this FLORA project and upload the image directly in FLORA:
https://app.flora.ai/projects/prj_XXXX. Once the image is available in FLORA, come back here and I can continue.
Use it as a Technique input
Section titled “Use it as a Technique input”The completed url is HTTPS, long-lived, and acceptable as any imageUrl or videoUrl Technique input. Just pass it:
const run = await client.techniques.runs.create('portrait-enhancer', { inputs: [ { id: 'input_image', type: 'imageUrl', value: imageUrl }, ], mode: 'async',});- Wait for
status: 'ready'before referencing the asset URL in a Technique run. Using it earlier may return404or409. - Upload before you need it. The reserve→upload→complete dance takes 1-2 seconds end-to-end, so don’t put it in the critical path of an interactive UI without a loading state.
- For batch uploads (50+ files), run the uploads in parallel with a bounded concurrency (e.g. 4 at a time) to avoid rate limits.
Related
Section titled “Related”- Generate a grid — use the uploaded asset as an input.
- Iterate on outputs — combine uploaded references with generated outputs.