Developer Documentation

Public API v1

A REST API to upload and convert files from your applications. Stable contract, webhooks, a simulator sandbox and idempotent requests.

Base URL https://api.perefile.com/v1 Open OpenAPI reference ->

# Introduction

The Perefile API v1 is a REST interface over HTTPS. Request and response bodies are JSON (except multipart file upload and result download). All endpoints live under the base URL and are versioned with the /v1 prefix.

Every response carries an X-Livemode header and a livemode field in the body: true means live mode (consumes quota and real conversions), false means sandbox (simulator).

ℹ️

The .com and .ru base URLs are equivalent and served by the same service. Use whichever is closer to your infrastructure; billing and quota are shared across the account.

# Authentication

Every request requires your API key in the X-API-Key header. An alternative is Authorization: Bearer <key>. Keys are created in your dashboard under the API section and shown only once - store them securely.

  • Live keys start with pf_live_ - available on plans with API access.
  • Test keys start with pf_test_ - always work and lead to the simulator sandbox.
  • A key can be revoked at any time in the dashboard; revocation takes effect within a minute.
cURL
curl https://api.perefile.com/v1/me \
  -H "X-API-Key: pf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
  "plan": "api_start",
  "livemode": true,
  "quota": { "limit": 1000, "used": 137, "reset_at": "2026-07-12T09:00:00Z" },
  "rate_limits": { "per_minute": 60, "per_day": 5000, "max_concurrent": 10 },
  "api_keys_active": 2
}
ℹ️

Never expose a live key in browser code or a mobile app. Requests to /v1 must come from your server only. CORS for /v1 is intentionally disabled.

# Quickstart

The minimal upload-and-convert flow is two requests: first upload the file and get a file_id, then create a conversion. Fetch the finished result from download_url.

cURL
# 1. Upload file -> file_id
curl https://api.perefile.com/v1/files \
  -H "X-API-Key: $PEREFILE_KEY" \
  -F "file=@document.docx"

# 2. Convert docx -> pdf
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": "file_abc123", "target_format": "pdf" }'
Python
import requests

BASE = "https://api.perefile.com/v1"
HEAD = {"X-API-Key": "pf_live_xxx"}

# 1. Upload
with open("document.docx", "rb") as f:
    up = requests.post(f"{BASE}/files", headers=HEAD, files={"file": f})
file_id = up.json()["file_id"]

# 2. Convert
conv = requests.post(
    f"{BASE}/conversions", headers=HEAD,
    json={"input": file_id, "target_format": "pdf"},
)
print(conv.json()["id"], conv.json()["status"])

# Available formats

GET /v1/formats returns the list of supported source -> target format pairs and the operation (convert, merge, split and others). The list is cached for 5 minutes.

cURL
curl https://api.perefile.com/v1/formats -H "X-API-Key: $PEREFILE_KEY"
Response
{
  "pairs": [
    { "from": "docx", "to": "pdf", "operation": "convert" },
    { "from": "pdf", "to": "docx", "operation": "convert" },
    { "from": "pdf", "to": "pdf", "operation": "merge" }
  ]
}

# Uploading files

POST /v1/files accepts a file in two ways: a multipart form (the file field) or JSON with a url field (fetch by link, with SSRF protection). The response contains a file_id, name, size, detected format and time to live (expires_at, 24 hours). The file_id is reusable: one file can be converted several times.

File
curl https://api.perefile.com/v1/files \
  -H "X-API-Key: $PEREFILE_KEY" \
  -F "file=@report.docx"
By URL
curl https://api.perefile.com/v1/files \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/report.docx" }'
Response
{
  "file_id": "file_abc123",
  "name": "report.docx",
  "size": 248173,
  "detected_format": "docx",
  "expires_at": "2026-06-15T09:00:00Z",
  "livemode": true
}
ℹ️

The format is detected from the content (magic bytes), not the extension. An oversized file or unsupported type is rejected immediately, before a conversion is created.

# Creating conversions

POST /v1/conversions creates a conversion. The input field is a file_id (string), an array of file_id (merge only), or a { url } object. Optionally: operation (convert by default), target_format and arbitrary settings.

Merging files (merge)

Pass an array of several file_id values with operation: merge. Merging N files into one result counts as a single conversion.

Sugar: upload and convert in one request

If you need to convert a single file and would rather not make two requests, send multipart directly to /v1/conversions with the file and target_format fields.

Single file
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": "file_abc123", "target_format": "pdf" }'
Merge
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "input": ["file_a", "file_b", "file_c"],
        "operation": "merge",
        "target_format": "pdf"
      }'
Sugar
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -F "file=@report.docx" \
  -F "target_format=pdf"
Response
{
  "id": "3f1c2e4a-0b9d-4c7e-8a1f-2d6b5c4e3a21",
  "status": "queued",
  "created_at": "2026-06-14T09:00:00Z",
  "poll_url": "https://api.perefile.com/v1/conversions/3f1c2e4a-0b9d-4c7e-8a1f-2d6b5c4e3a21",
  "livemode": true
}

# Conversion settings

The settings field in POST /v1/conversions is an optional object of processing parameters. The set of keys depends on the operation and format category; unrecognized keys are ignored. Below are the common parameters; the full per-category reference (the *Settings schemas) is in the reference at /developers/reference.

  • PDF: pdf_password (password on the result), source_password (password of the source PDF), pages and split_mode (splitting), compress_level (screen/ebook/printer).
  • Images: quality (1-100 or low/medium/high/maximum), width and height, maintain_aspect, dpi (rasterization).
  • Video: video_codec (h264/h265/vp9/av1), video_resolution (1080p/720p/...), video_bitrate, video_fps.
  • Audio: audio_codec (aac/mp3/opus/flac), audio_bitrate, audio_sample_rate (44100/48000/...).
  • Documents and ebooks: paper_size, paper_orientation, toc, font_size; OCR: ocr_languages, ocr_quality; archives: compression_level, password.
Password-protect a PDF
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "input": "file_abc123",
        "target_format": "pdf",
        "settings": { "pdf_password": "s3cret" }
      }'
Compress and resize an image
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "input": "file_img1",
        "target_format": "webp",
        "settings": { "quality": 80, "width": 1280, "maintain_aspect": true }
      }'
ℹ️

settings is passed through as-is: unknown parameters do not cause an error, they are ignored. The exact keys, ranges and defaults per category are in the reference at /developers/reference.

# Getting the result

A conversion runs asynchronously. There are two ways to learn when it is ready: polling and webhooks. Short conversions (up to ~30 seconds) are convenient to poll via poll_url; for longer ones, subscribe to webhooks and do not hold the connection.

Lifecycle and polling

  • Statuses: queued (accepted) -> processing -> completed (ready) or failed (reason in error.code). A conversion may be cancelled. Completed, failed and cancelled are final.
  • Poll poll_url until a final status: start with a 1-2 second interval and increase it (exponential backoff), no more than once per second.
  • A typical conversion takes seconds, large files take tens of seconds. For long jobs use webhooks instead of holding the connection.
  1. GET /v1/conversions/{id} returns status, progress (0-100) and, when status=completed, a download_url with expires_at.
  2. GET /v1/conversions/{id}/download returns the result: a 302 to a temporary link or a stream. For split operations the result is a single ZIP.
  3. download_url points to our /v1 endpoint and requires X-API-Key - it is safe to pass around (unlike presigned links).
Status
curl https://api.perefile.com/v1/conversions/3f1c2e4a-... \
  -H "X-API-Key: $PEREFILE_KEY"
Download
curl -L https://api.perefile.com/v1/conversions/3f1c2e4a-.../download \
  -H "X-API-Key: $PEREFILE_KEY" \
  -o result.pdf
Response
{
  "id": "3f1c2e4a-...",
  "status": "completed",
  "progress": 100,
  "source_format": "docx",
  "target_format": "pdf",
  "created_at": "2026-06-14T09:00:00Z",
  "completed_at": "2026-06-14T09:00:07Z",
  "expires_at": "2026-06-15T09:00:07Z",
  "download_url": "https://api.perefile.com/v1/conversions/3f1c2e4a-.../download",
  "livemode": true
}

# Webhooks

Webhooks are the primary way to learn about finished conversions. Set a webhook_url for the key in your dashboard; on creation you receive a webhook_secret once, used to verify the signature. Events: conversion.completed and conversion.failed.

  • Delivery: POST to your URL, 10-second timeout, up to 5 retries with increasing backoff (1m, 5m, 30m, 2h, 6h).
  • Headers: X-Perefile-Event (event type), X-Livemode and X-Perefile-Signature (HMAC-SHA256 of the body with webhook_secret).
  • In the event body, download_url points to our /v1 endpoint and requires X-API-Key - it is not a presigned link.
Event body
{
  "event": "conversion.completed",
  "livemode": true,
  "conversion": {
    "id": "3f1c2e4a-...",
    "status": "completed",
    "source_format": "docx",
    "target_format": "pdf",
    "created_at": "2026-06-14T09:00:00Z",
    "completed_at": "2026-06-14T09:00:07Z",
    "download_url": "https://api.perefile.com/v1/conversions/3f1c2e4a-.../download"
  }
}
Python
import hmac, hashlib

def verify(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# In your webhook handler:
#   raw = request.get_data()                       # raw body, do not parse first
#   sig = request.headers["X-Perefile-Signature"]
#   if not verify(raw, sig, WEBHOOK_SECRET): abort(400)
Node.js
import crypto from 'node:crypto';

function verify(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

// rawBody must be the raw request bytes (express.raw), not JSON.parse.
ℹ️

Verify the signature against the raw request body before parsing it. On a signature mismatch, reject the request with status 400. Respond 2xx as fast as possible; move heavy processing to a background queue.

# Sandbox

A test key (pf_test_) leads to the simulator sandbox: the contract is real, the result is a fake sample. Workers are not engaged, quota and billing are not consumed, and livemode is false. The sandbox tests your integration, not conversion quality.

  • Validations are real: magic-byte detection, format-pair support, size limit. An invalid file is rejected.
  • The downloaded result is a static sample of the target format marked PEREFILE SANDBOX; split returns a sample ZIP.
  • Magic scenarios: a filename prefixed with fail- yields a conversion.failed event; slow- completes at the 30th second.
  • Webhooks and Idempotency-Key work for real. Flood limits: 60 requests/min and 1000 simulations/day.
cURL
# Test key -> sandbox
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: pf_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@fail-document.docx" \
  -F "target_format=pdf"
# filename prefixed with fail- -> conversion.failed

# Idempotency

POST /v1/conversions supports the Idempotency-Key header (up to 255 characters). A repeated request with the same key returns the very same conversion (with an Idempotency-Replayed: true header) and does not consume quota again. The key lives for 24 hours. Use it when retrying after network failures.

cURL
curl https://api.perefile.com/v1/conversions \
  -H "X-API-Key: $PEREFILE_KEY" \
  -H "Idempotency-Key: order-9c3f-2026-06-14" \
  -H "Content-Type: application/json" \
  -d '{ "input": "file_abc123", "target_format": "pdf" }'

# Limits and quota

The unit of accounting is a conversion (merging several files = one conversion). A package applies to the subscription window. GET /v1/me returns the current window state, and every conversion-creation response carries quota headers.

  • X-Quota-Limit, X-Quota-Used, X-Quota-Reset - the limit, used amount and window reset time.
  • Safeguards: a per-minute request limit, a daily cap and a concurrent-conversion limit (max_concurrent).
  • When a limit is exhausted or exceeded, the response is 429 with Retry-After (seconds until the window ends) and quota details.
429
{
  "error_code": "quota_exceeded",
  "message": "The conversion package for the current window is exhausted",
  "livemode": true,
  "retry_after": 81234,
  "quota": { "limit": 1000, "used": 1000, "reset_at": "2026-07-12T09:00:00Z" },
  "upgrade_url": "https://perefile.com/pricing",
  "request_id": "req_7f3a..."
}

# Billing model

A plan includes a package of conversions for the subscription window. Beyond the package, overage is possible with a per-conversion surcharge at the plan rate - invoiced when the window closes. Overage is controlled by a toggle in your dashboard; with overage off, requests get 429 once the package is exhausted.

  • Web conversions and the sandbox do not count toward the API quota.
  • Once the overage cap is reached, requests get 429 - the way out is a plan upgrade.
  • Current packages and prices are on the pricing page; invoice history is in your dashboard.

# Errors

Errors are returned with the appropriate HTTP status and a uniform body: error_code (machine-readable code), message (human-readable description), livemode, plus docs_url and request_id. React to error_code, not the message text; include the request_id when contacting support. The full catalog of codes with HTTP statuses is in the reference at /developers/reference.

How to react by status

  • 401 (invalid_api_key, api_key_revoked, api_key_expired) - check the header and key, create a new one if needed.
  • 403 (plan_does_not_include_api, overage_unpaid) - get a plan with API access or pay the overage invoice.
  • 400 (invalid_settings, unsupported_conversion, file_too_large, invalid_url and others) - fix the request; repeating it unchanged returns the same error.
  • 404 (not_found) - check the identifier and that the resource belongs to this key.
  • 409 (idempotency_in_progress) - the request with this Idempotency-Key is still processing; retry later with the same key.
  • 429 (quota_exceeded, rate_limit_exceeded, too_many_active_conversions, quota_cap_reached) - honor Retry-After, check GET /me, switch plan when the package is exhausted.
  • 5xx (internal_error, service_unavailable) - a transient failure; retry with exponential backoff.
Error body
{
  "error_code": "file_not_found",
  "message": "File not found or expired",
  "livemode": true,
  "docs_url": "https://perefile.com/developers#files",
  "request_id": "req_7f3a9c..."
}

# Versioning

The version is pinned in the path - /v1. Within v1 we make only backward-compatible changes: we add new endpoints, fields and enum values. Build for this: ignore unknown fields in responses and do not rely on their order.

  • Breaking changes will ship only under a new path version (for example, /v2).
  • The full machine-readable contract is the interactive OpenAPI reference at /developers/reference (raw spec: /openapi-public-v1.yaml).
  • We announce upcoming changes and support timelines in advance via your account contacts.

Ready to start?

Create an API key in your dashboard and try requests in the sandbox - without spending quota.

Go to dashboard