Skip to content

Upload a file, ask for an operation, receive the result. Three calls, none of which block on the work.

Fylane never moves file bytes through the API. You ask for a place to put a file, you upload it directly to that place, and you ask for work to be done on it. That is the whole shape, and it is why a 2 GB file costs the same to accept as a 2 KB one.

One call, with the SDK

The Node SDK owns all three steps — the upload dance, the job, the polling — and returns the finished job. Everything below remains true and is exactly what the SDK does on your behalf.

The whole loop in one call
import { Fylane } from '@fylane/node';

const fylane = new Fylane(process.env.FYLANE_API_KEY);

const job = await fylane.files.process('./report.docx', { operation: 'convert' });
console.log(job.state, job.artifacts);
A failed job is a result, not an exception
The SDK returns a job in state failed with the reason on job.error. Only transport and request errors throw — as FylaneApiError, carrying the same code, message and request id the API sent.

The three steps

  1. POST /v1/uploads returns a pre-signed URL that is single-use, size-capped and short-lived.
  2. PUT the bytes straight to that URL. Nothing about this step touches Fylane compute.
  3. POST /v1/jobs asks for an operation. It returns 202 and a job, not a result.

The third step returns immediately because anything slower than roughly three seconds is asynchronous by design. Subscribe a webhook or poll the job; both are documented below.

Create an upload session

curl -X POST https://api.fylane.dev/v1/uploads \
  -H "Authorization: Bearer fy_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "report.pdf", "byte_size": 482113, "mime_type": "application/pdf" }'
Response — 201 Created
{
  "id": "ups_01JBQ8Z5T7WXK9MNP2RSTVA3C4",
  "object": "upload_session",
  "file_id": "file_01JBQ8Z5T7WXK9MNP2RSTVA3C5",
  "url": "https://uploads.fylane.dev/...",
  "method": "PUT",
  "max_bytes": 482113,
  "status": "pending",
  "expires_at": "2026-08-20T15:03:11Z"
}

Upload the bytes

curl -X PUT "$UPLOAD_URL" \
  --upload-file report.pdf \
  -H "Content-Type: application/pdf"

The signature enforces max_bytes. A larger body is refused by storage rather than by us, which is the point — an oversized upload never reaches anything that could be made to do work.

Ask for work

curl -X POST https://api.fylane.dev/v1/jobs \
  -H "Authorization: Bearer fy_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "file_id": "file_01JBQ8Z5T7WXK9MNP2RSTVA3C5", "preset": "ai-ready" }'
TypeScript, without the SDK
const upload = await fetch('https://api.fylane.dev/v1/uploads', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.FYLANE_KEY}`,
    'idempotency-key': crypto.randomUUID(),
    'content-type': 'application/json',
  },
  body: JSON.stringify({ filename: 'report.pdf', byte_size: bytes.byteLength }),
}).then((response) => response.json());

await fetch(upload.url, { method: 'PUT', body: bytes });

const job = await fetch('https://api.fylane.dev/v1/jobs', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.FYLANE_KEY}`,
    'idempotency-key': crypto.randomUUID(),
    'content-type': 'application/json',
  },
  body: JSON.stringify({ file_id: upload.file_id, preset: 'ai-ready' }),
}).then((response) => response.json());

// job.state === 'queued'. Subscribe a webhook rather than polling in a loop.
Python, without the SDK
import os, uuid, httpx

headers = {
    "authorization": f"Bearer {os.environ['FYLANE_KEY']}",
    "idempotency-key": str(uuid.uuid4()),
}

upload = httpx.post(
    "https://api.fylane.dev/v1/uploads",
    headers=headers,
    json={"filename": "report.pdf", "byte_size": len(data)},
).json()

httpx.put(upload["url"], content=data)

job = httpx.post(
    "https://api.fylane.dev/v1/jobs",
    headers={**headers, "idempotency-key": str(uuid.uuid4())},
    json={"file_id": upload["file_id"], "preset": "ai-ready"},
).json()

Getting the result

Register a webhook endpoint and let Fylane tell you. Polling works and is documented, but a poll loop against a job that takes four minutes is four minutes of requests that all say "not yet".

ApproachUse whenCost
WebhookAnything in productionOne signed request per event
Poll GET /v1/jobs/{id}Scripts, local developmentOne request per poll, rate limited like any other

Every mutating call takes an idempotency key

A replay with the same body returns the stored response without re-executing and without re-billing. A replay with a different body returns 409 idempotency_conflict. Keys are scoped to your organisation and the endpoint, and retained for 24 hours.

Retries are your responsibility to make safe, and ours to honour
A network timeout does not tell you whether the request arrived. Retrying with the same idempotency key is always safe; retrying without one may cost you twice.