Authentication
All /api/v1 endpoints require an API key in the header:
X-API-Key: pdfx_your_key_here
Create keys using the Key Management endpoints below.
Rate Limiting
Each API key is limited to 60 requests per 60 seconds.
Every response includes these headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per window (60) |
X-RateLimit-Remaining | Requests remaining in this window |
X-RateLimit-Reset | Unix timestamp when window resets |
When exceeded, the API returns 429 Too Many Requests.
Concurrent Extraction Limit
Each API key can have at most 5 active extractions (status queued or processing) at once. If you hit this limit, the API returns 429 with code concurrent_limit.exceeded. Wait for in-progress jobs to finish before submitting more.
Admin Endpoints
Key management endpoints (POST /keys, GET /keys, DELETE /keys/{id}) have a stricter limit: 5 requests per 60 seconds per key.
Response Format
Every response uses a consistent envelope:
Success:
{
"status": "success",
"data": { ... },
"meta": {
"request_id": "abc123",
"timestamp": "2026-02-14T12:00:00+00:00",
"version": "1.0"
}
}
Error:
{
"status": "error",
"error": {
"code": "auth.invalid_key",
"message": "Invalid API key."
},
"meta": {
"request_id": "abc123",
"timestamp": "2026-02-14T12:00:00+00:00",
"version": "1.0"
}
}
Endpoints
Upload & Extract PDF
Upload a PDF for data extraction. Returns immediately with a job ID.
Request
Content-Type: multipart/form-data
| Field | Type | Description |
|---|---|---|
file | File | PDF file (max 32 MB, max 100 pages) |
Response — 202 Accepted
{
"status": "success",
"data": {
"id": 1,
"filename": "application.pdf",
"job_status": "queued",
"message": "Extraction job submitted. Poll GET /api/v1/extractions/{id} for results."
}
}
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | validation.error | Not a PDF, empty, or too large |
| 401 | auth.missing_key | No X-API-Key header |
| 401 | auth.invalid_key | Key not recognized |
| 429 | rate_limit.exceeded | Too many requests |
| 429 | concurrent_limit.exceeded | 5 active jobs already running |
| 507 | storage.insufficient | Not enough free disk space |
Get Extraction Results
Retrieve full results for a specific extraction.
Status progression: queued → processing → completed | failed
When status is failed, error_message has a human-readable description and error_code has a structured code (see Error Codes).
Response — 200 OK
{
"status": "success",
"data": {
"id": 1,
"filename": "application.pdf",
"form_type": "ACORD 125",
"carrier": "Example Insurance Co",
"insurer": "National Underwriters",
"product": "Employment Practices Liability",
"application_name": "EPL Application",
"application_id": "EPL-APP-2024",
"carrier_product_name": "Example EPL Shield",
"application_version": "Rev. 01/2024",
"field_count": 42,
"duration_ms": 8432,
"job_status": "completed",
"error_message": null,
"error_code": null,
"api_key_id": 3,
"created_at": "2026-02-14T12:00:00",
"fields": [
{
"field_name": "Insured Name",
"value": "Acme Corp",
"section": "General Information",
"page": 1
}
]
}
}
Errors
| Status | Code | Cause |
|---|---|---|
| 404 | resource.not_found | Extraction ID invalid |
List Extractions
List past extractions, most recent first. Supports pagination.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number (1-based) |
page_size | int | 50 | Items per page (max 200) |
Response — 200 OK
{
"status": "success",
"data": {
"extractions": [
{
"id": 1,
"filename": "application.pdf",
"form_type": "ACORD 125",
"carrier": "Example Insurance Co",
"field_count": 42,
"duration_ms": 8432,
"job_status": "completed",
"api_key_id": 3,
"created_at": "2026-02-14T12:00:00"
}
],
"total": 1,
"page": 1,
"page_size": 50,
"total_pages": 1
}
}
Download Extraction as CSV
Get the extracted data formatted as CSV.
The CSV includes a Metadata section (form type, carrier, insurer, product, application name, application ID, carrier product name, application version) followed by all extracted fields with columns: Section, Field Name, Value, Page.
Response — 200 OK
{
"status": "success",
"data": {
"csv": "Section,Field Name,Value,Page\nMetadata,Form Type,ACORD 125,\n...",
"filename": "application_extracted.csv"
}
}
Create API Key
Admin rate limit: Key management endpoints are limited to 5 requests per 60 seconds per key.
Generate a new API key. The raw key is returned only once — store it securely.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Label for this key |
Response — 201 Created
{
"status": "success",
"data": {
"name": "My Key Name",
"key": "pdfx_a1b2c3d4e5f6...",
"created_at": "2026-02-14T12:00:00"
}
}
List API Keys
List all API keys with metadata. Never returns the full key — only the 8-character prefix.
Response — 200 OK
{
"status": "success",
"data": [
{
"id": 1,
"name": "My Key Name",
"key_prefix": "pdfx_a1b",
"is_active": true,
"created_at": "2026-02-14T12:00:00",
"last_used_at": "2026-02-14T14:30:00"
}
]
}
Revoke API Key
Soft-revoke an API key. It will no longer authenticate. The record is kept for audit purposes.
Response — 200 OK
{
"status": "success",
"data": {
"id": 1,
"revoked": true
}
}
Typical Workflow
-
Create an API key
POST /api/v1/keys?name=MyApp
Save the returned key — it's shown only once. -
Upload a PDF
POST /api/v1/extractwithX-API-Keyheader
You'll get back a job ID immediately. -
Poll for results
GET /api/v1/extractions/{id}
Repeat untiljob_statusiscompletedorfailed. -
Download CSV (optional)
GET /api/v1/extractions/{id}/csv
cURL Examples
Extract a PDF
curl -X POST https://api.prowritersservices.com/api/v1/extract \
-H "X-API-Key: pdfx_your_key_here" \
-F "file=@application.pdf"
Check extraction status
curl https://api.prowritersservices.com/api/v1/extractions/1 \
-H "X-API-Key: pdfx_your_key_here"
Download CSV
curl https://api.prowritersservices.com/api/v1/extractions/1/csv \
-H "X-API-Key: pdfx_your_key_here"
Error Codes
HTTP Error Codes
| Code | HTTP | Description |
|---|---|---|
auth.missing_key | 401 | No X-API-Key header provided |
auth.invalid_key | 401 | API key not recognized |
auth.revoked_key | 403 | API key has been revoked |
validation.error | 400 | Invalid request or file |
resource.not_found | 404 | Extraction or key not found |
rate_limit.exceeded | 429 | Too many requests, try again shortly |
concurrent_limit.exceeded | 429 | Too many active extractions for this key |
storage.insufficient | 507 | Not enough free disk space to accept uploads |
server.internal_error | 500 | Unexpected server error |
Extraction Error Codes
These appear in the error_code field of a failed extraction (via GET /api/v1/extractions/{id}), not as HTTP response codes:
| Code | Description |
|---|---|
extraction.api_error | Claude API call failed |
extraction.token_limit | Input exceeded Claude's token limit |
extraction.invalid_pdf | PDF could not be read or parsed |
extraction.timeout | Chunk processing exceeded 10-minute timeout |
extraction.no_structured_data | Claude returned no structured extraction results |
extraction.internal | Unexpected error during extraction |
Health Check
No authentication required. Returns server and database/Redis connectivity status.
Response — 200 OK
{
"status": "healthy",
"checks": {
"database": "connected",
"redis": "connected"
},
"version": "1.0"
}