Get Generation Status
The GET /file/status/{requestId} endpoint returns the status of a PDF generation request (queued / processing / completed / failed). Use it to wait for async generation and to find out why a request failed. A completed request includes a download path for each file, and a failed one includes an error code and reason, so you no longer need to guess completion by calling the download endpoint repeatedly.
Endpoint Information
- URL:
https://api.re-port-flow.com/v1/file/status/{requestId} - Method:
GET - Authentication: Same as the other endpoints (
appkeyheader, orAuthorization: Bearer <access token>) - Rate limit: 300 req/min (per workspace; separate from the download API's 100 req/min)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
requestId | string | ✓ | The requestId from the generation response (16-character alphanumeric; path parameter) |
Usage Examples
cURL
curl -i https://api.re-port-flow.com/v1/file/status/kZ3mP9qL2xV7nR4t \
-H "appkey: your-application-key"
JavaScript
async function waitForCompletion(requestId, { timeoutMs = 300000 } = {}) {
const deadline = Date.now() + timeoutMs;
let notFoundRetries = 0;
for (;;) {
// Apply the deadline to each request and wait too (axios treats timeout: 0 as "no timeout")
const remainingMs = deadline - Date.now();
if (remainingMs <= 0) break;
let res;
try {
res = await axios.get(
`https://api.re-port-flow.com/v1/file/status/${requestId}`,
{ headers: { 'appkey': process.env.APP_KEY }, timeout: remainingMs }
);
} catch (e) {
// A 404 right after generating may be a request still in progress whose job record could not be saved. Re-check up to twice
if (e.response?.status === 404 && notFoundRetries < 2) {
notFoundRetries += 1;
await new Promise(resolve => setTimeout(resolve, Math.min(3000, Math.max(0, deadline - Date.now()))));
continue;
}
// On 429 (the status API allows 300 req/min), wait Retry-After seconds and check again
if (e.response?.status === 429) {
const waitMs = Number(e.response.headers['retry-after'] ?? 60) * 1000;
await new Promise(resolve => setTimeout(resolve, Math.min(waitMs, Math.max(0, deadline - Date.now()))));
continue;
}
throw e;
}
if (res.data.status === 'completed') return res.data;
if (res.data.status === 'failed') {
throw new Error(`${res.data.error.code}: ${res.data.error.message}`);
}
// While queued / processing, the response carries Retry-After (seconds)
const waitMs = Number(res.headers['retry-after'] ?? 2) * 1000;
await new Promise(resolve => setTimeout(resolve, Math.min(waitMs, Math.max(0, deadline - Date.now()))));
}
throw new Error(`Timed out: ${requestId}`);
}
Python
import os
import time
import requests
def wait_for_completion(request_id, timeout_sec=300):
deadline = time.time() + timeout_sec
not_found_retries = 0
while True:
# Apply the deadline to the request and the wait too (requests without timeout waits forever)
remaining = deadline - time.time()
if remaining <= 0:
break
res = requests.get(
f"https://api.re-port-flow.com/v1/file/status/{request_id}",
headers={'appkey': os.getenv('APP_KEY')},
timeout=remaining,
)
# A 404 right after generating may be a request still in progress whose job record could not be saved. Re-check up to twice
if res.status_code == 404 and not_found_retries < 2:
not_found_retries += 1
time.sleep(min(3, max(0, deadline - time.time())))
continue
# On 429 (the status API allows 300 req/min), wait Retry-After seconds and check again
if res.status_code == 429:
time.sleep(min(int(res.headers.get('Retry-After', '60')), max(0, deadline - time.time())))
continue
res.raise_for_status()
body = res.json()
if body['status'] == 'completed':
return body
if body['status'] == 'failed':
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
# While queued / processing, the response carries Retry-After (seconds)
time.sleep(min(int(res.headers.get('Retry-After', '2')), max(0, deadline - time.time())))
raise TimeoutError(request_id)
Response
In progress (200 OK)
HTTP/1.1 200 OK
Retry-After: 2
Content-Type: application/json
{
"requestId": "kZ3mP9qL2xV7nR4t",
"status": "processing",
"designId": "0eUDdgAjNXrrItA2",
"version": 3,
"files": [],
"createdAt": "2026-10-01T00:00:00.000Z"
}
While the request is queued / processing, the response carries a Retry-After: 2 header. Use it as your polling interval.
Completed (200 OK)
{
"requestId": "kZ3mP9qL2xV7nR4t",
"status": "completed",
"designId": "0eUDdgAjNXrrItA2",
"version": 3,
"files": [
{
"fileId": "Hn4sT8wQ1cV6bX2e",
"fileName": "invoice.pdf",
"pageCount": 1,
"downloadPath": "/v1/file/download/kZ3mP9qL2xV7nR4t/Hn4sT8wQ1cV6bX2e"
}
],
"createdAt": "2026-10-01T00:00:00.000Z",
"completedAt": "2026-10-01T00:00:05.000Z"
}
downloadPath is relative to the API host (https://api.re-port-flow.com) and already includes /v1. Append it to the host, not to the base URL (https://api.re-port-flow.com/v1), or you get /v1/v1/.... A GET with your authentication header returns the PDF (see PDF Download).
If only some files of a multiple request failed, status is still completed and the failed files are listed in failedFiles (fileId is the ID returned in the 202 response):
{
"requestId": "kZ3mP9qL2xV7nR4t",
"status": "completed",
"files": [
{ "fileId": "Hn4sT8wQ1cV6bX2e", "fileName": "invoice_001.pdf", "pageCount": 1, "downloadPath": "/v1/file/download/kZ3mP9qL2xV7nR4t/Hn4sT8wQ1cV6bX2e" }
],
"failedFiles": [
{ "fileId": "Rt7uY2iO9pA4sD6f", "fileName": "invoice_002.pdf", "code": "INTERNAL_ERROR", "message": "PDF generation failed due to an internal error." }
],
"...": "..."
}
Failed (200 OK)
{
"requestId": "kZ3mP9qL2xV7nR4t",
"status": "failed",
"designId": "0eUDdgAjNXrrItA2",
"version": 3,
"files": [],
"failedFiles": [
{
"fileId": "Hn4sT8wQ1cV6bX2e",
"fileName": "invoice.pdf",
"code": "PARAMS_TYPE_MISMATCH",
"message": "Parameter validation failed: \"amount\" must be a number"
}
],
"error": {
"code": "PARAMS_TYPE_MISMATCH",
"message": "Parameter validation failed: \"amount\" must be a number"
},
"createdAt": "2026-10-01T00:00:00.000Z",
"completedAt": "2026-10-01T00:00:01.000Z"
}
error.code is one of the codes on Error Codes (for example PARAMS_TYPE_MISMATCH, FILE_GENERATION_FAILED, INTERNAL_ERROR, GENERATION_INTERRUPTED). error.message is in English. The monthly page limit (PLAN_PAGE_LIMIT_EXCEEDED) is checked before the request is accepted — the generation endpoint returns 403 — so it never appears here.
When an async generation fails, the per-file reasons are also listed in failedFiles (one item for a single request; when every file of a multiple request fails, the reasons can differ per file and error.code is FILE_GENERATION_FAILED).
Response fields
| Field | Type | Description |
|---|---|---|
requestId | string | Request ID |
status | string | queued / processing / completed / failed |
designId | string | Template ID |
version | integer | Template version |
files | array | Completed files (fileId / fileName / pageCount / downloadPath). Empty until completed |
failedFiles | array | Per-file failure reasons (fileId / fileName / code / message; fileId is omitted when it was not recorded). Returned on a partial success of a multiple request (completed) and on a failed async generation (failed). Omitted when nothing failed |
error | object | The reason when status is failed (code / message) |
createdAt | string | When the request was accepted (ISO 8601) |
completedAt | string | When it completed or failed (ISO 8601). Only for completed / failed |
queuedqueued is a reserved value. The current implementation starts generating immediately after accepting a request, so you will normally see processing, completed or failed.
How long status is kept
- The status of an async generation (its job record) is kept for 24 hours after it completes or fails.
- After that (and for sync generation requests), a request whose generated files still exist is reported as
completed(built from the file records, so it mainly containsdesignIdandfiles;version,pageCount,createdAtandcompletedAtare not included). - A failure older than 24 hours is reported as follows.
- A multiple request in which every file failed: still 200 with
status: "failed"anderror.codeFILE_GENERATION_FAILED. The per-file reasons (failedFiles) are not included. - A failed single request: a failed request has no file record, so it returns 404
REQUEST_NOT_FOUND.
- A multiple request in which every file failed: still 200 with
- A request whose files were generated successfully but later deleted, with no files left, also returns 404
REQUEST_NOT_FOUND.
Error Responses
| Status | code | Cause |
|---|---|---|
| 404 | REQUEST_NOT_FOUND | The requestId does not exist, belongs to another workspace, is a failed single request older than 24 hours, or its generated files have been deleted and none are left. Right after generating, a request whose job record could not be saved can return 404 while still in progress (re-check once or twice a few seconds apart) |
| 429 | RATE_LIMITED | More than 300 req/min. Wait Retry-After seconds and resend |
{
"statusCode": 404,
"code": "REQUEST_NOT_FOUND",
"message": "Request not found: kZ3mP9qL2xV7nR4t",
"error": "Not Found",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#request_not_found"
}
Next Steps
- Async Workflows - Patterns for waiting with the status API
- PDF Download - Fetch completed files
- Webhook Notifications - Receive completion and failure without polling
- Error Codes