Quickstart
View as MarkdownUpload 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.
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);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
POST /v1/uploadsreturns a pre-signed URL that is single-use, size-capped and short-lived.PUTthe bytes straight to that URL. Nothing about this step touches Fylane compute.POST /v1/jobsasks for an operation. It returns202and 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 POST https://api.fylane.dev/v1/uploads \
"Authorization: Bearer fy_test_..." \
"Idempotency-Key: $(uuidgen)" \
"Content-Type: application/json" \
'{ "filename": "report.pdf", "byte_size": 482113, "mime_type": "application/pdf" }'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 PUT "$UPLOAD_URL" \
report.pdf \
"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 POST https://api.fylane.dev/v1/jobs \
"Authorization: Bearer fy_test_..." \
"Idempotency-Key: $(uuidgen)" \
"Content-Type: application/json" \
'{ "file_id": "file_01JBQ8Z5T7WXK9MNP2RSTVA3C5", "preset": "ai-ready" }'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.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".
| Approach | Use when | Cost |
|---|---|---|
| Webhook | Anything in production | One signed request per event |
Poll GET /v1/jobs/{id} | Scripts, local development | One 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.