REST API
OAuth 2.0 and REST API v1 reference for parsers, documents, results, and webhook subscriptions.
The Parsedit REST API (v1) lets partner apps and your own software create documents, read extraction results, and subscribe to signed webhook events. This page matches the public surface in production.
Base URLs
| Purpose | Host |
|---|---|
| Marketing site, docs, OAuth authorize (browser session cookies) | https://www.parsedit.com |
| Token exchange, refresh, and REST API | https://api.parsedit.com |
Examples below use the API host for Bearer-authenticated calls:
https://api.parsedit.com/api/v1/...
Authorize in the browser on www only. Do not point the authorize URL at api.* — session cookies live on www.
Authentication
All v1 endpoints require a Bearer access token:
Authorization: Bearer <access_token>
Tokens are issued through OAuth 2.0 (Authorization Code + PKCE S256). Each token is scoped to one workspace (personal or team) and a set of OAuth scopes. Revoke a connected application, and all of its tokens, under Integrations → Connected applications.
| Status | Meaning |
|---|---|
401 | Missing, expired, revoked, or otherwise invalid token ({ "error": "Unauthorized" }) |
403 | Valid token but missing the required scope ({ "error": "Insufficient scope" }) |
429 | Rate limited (read and write budgets are separate; see Rate limits) |
OAuth authorize
Send the user to:
https://www.parsedit.com/oauth/authorize
| Query param | Required | Description |
|---|---|---|
client_id | Yes | Your OAuth client id |
redirect_uri | Yes | Must match a registered redirect URI for the client |
response_type | Yes | Must be code |
scope | Yes | Space-separated scopes (see Scopes) |
state | Recommended | Opaque CSRF value; returned on the redirect |
code_challenge | Yes | PKCE S256 challenge |
code_challenge_method | Yes | Must be S256 |
On approval, Parsedit redirects to redirect_uri with code and state. Authorization codes are short-lived and single-use.
Token and refresh
POST https://api.parsedit.com/api/oauth/token Content-Type: application/x-www-form-urlencoded
JSON bodies are also accepted (Content-Type: application/json). Client credentials may be sent in the body or via HTTP Basic auth.
Authorization code exchange
| Field | Required |
|---|---|
grant_type | authorization_code |
code | Yes |
redirect_uri | Yes (must match authorize) |
code_verifier | Yes (PKCE) |
client_id | Yes |
client_secret | Yes |
Refresh
| Field | Required |
|---|---|
grant_type | refresh_token |
refresh_token | Yes |
client_id | Yes |
client_secret | Yes |
Example response
{
"access_token": "…",
"refresh_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "account:read parsers:read documents:read documents:write webhooks:write"
}
Default access-token lifetime is 3600 seconds (1 hour). Refresh tokens rotate on use; reuse of a rotated refresh token revokes the grant family.
Common token errors
| HTTP | error | When |
|---|---|---|
401 | invalid_client | Missing or wrong client credentials |
400 | invalid_request | Missing code / redirect_uri / code_verifier or refresh_token |
400 | invalid_grant | Code/verifier mismatch, expired code, or invalid refresh |
400 | unsupported_grant_type | grant_type not authorization_code or refresh_token |
429 | temporarily_unavailable | Too many token requests |
Revoking access
End users revoke apps under Integrations → Connected applications. That invalidates outstanding tokens for that connection.
Scopes
| Scope | Allows |
|---|---|
account:read | GET /api/v1/me |
parsers:read | List/get parsers and parser fields |
documents:read | List/get documents and results |
documents:write | Create documents |
webhooks:write | Create and delete webhook subscriptions |
Request only the scopes your integration needs. Zapier typically requests all five.
Conventions
Pagination
Cursor pagination uses opaque cursor query values.
- Parsers list (
GET /api/v1/parsers): response body is a top-level JSON array. When another page exists, the response includes headerX-Next-Cursor. Pass that value as?cursor=on the next request. - Documents list (
GET /api/v1/documents): response body is{ "documents": [...], "next_cursor": "…" | null }. Passnext_cursoras?cursor=when non-null.
Default limit is 50. Maximum limit is 100. Invalid cursors return 400 with { "error": "Invalid cursor" }.
Rate limits
Per access token, per minute (defaults):
| Class | Default | Applies to |
|---|---|---|
Read (api_read) | 120/min | GET endpoints |
Write (api_write) | 30/min | POST/DELETE endpoints and OAuth token |
Exceeded limits return 429 with a Retry-After header when available.
Errors
Validation and not-found responses use JSON { "error": "<message>" } unless noted. OAuth token errors use RFC 6749-style { "error", "error_description?" }.
Endpoints
GET /api/v1/me
Authenticated account and token metadata. Used as the OAuth connection test.
| Scope | account:read |
| Rate limit | Read |
Example request
curl -sS https://api.parsedit.com/api/v1/me \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response
{
"name": "Acme Accounting",
"email": "owner@acme.example",
"account": {
"id": "11111111-1111-1111-1111-111111111111",
"name": "Acme Accounting",
"slug": "acme-accounting",
"is_personal_account": false
},
"user": {
"id": "22222222-2222-2222-2222-222222222222",
"email": "owner@acme.example"
},
"scopes": [
"account:read",
"parsers:read",
"documents:read",
"documents:write",
"webhooks:write"
],
"client_id": "your_oauth_client_id"
}
Errors: 401, 403.
GET /api/v1/parsers
List parsers for the authenticated workspace as a top-level JSON array (Zapier Search / dynamic dropdown compatible). Parsers still mid template setup are omitted.
| Scope | parsers:read |
| Rate limit | Read |
| Pagination | limit, cursor; next page via X-Next-Cursor |
| Query | Type | Description |
|---|---|---|
name | string | Optional case-insensitive name filter (substring) |
limit | integer | 1–100 (default 50) |
cursor | string | Opaque cursor from X-Next-Cursor |
Example request
curl -sS "https://api.parsedit.com/api/v1/parsers?limit=50" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -D -
Example response
[
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "Vendor invoices",
"document_type": "invoice",
"created_at": "2026-03-01T12:00:00.000Z",
"updated_at": "2026-03-10T09:30:00.000Z"
}
]
When more results exist:
X-Next-Cursor: eyJjcmVhdGVkX2F0IjoiLi4uIiwiaWQiOiIuLi4ifQ
Errors: 400 invalid query/cursor, 401, 403.
GET /api/v1/parsers/:id
Get a single parser. Includes the parser’s inbound email address when configured.
| Scope | parsers:read |
| Rate limit | Read |
Example request
curl -sS https://api.parsedit.com/api/v1/parsers/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "Vendor invoices",
"document_type": "invoice",
"created_at": "2026-03-01T12:00:00.000Z",
"updated_at": "2026-03-10T09:30:00.000Z",
"inbound_email": "parser-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@in.parsedit.com"
}
Errors: 400 missing id, 401, 403, 404 { "error": "Parser not found" }.
GET /api/v1/parsers/:id/fields
Ordered template fields for Zapier/Make mapping, including section labels and line-item columns (children).
| Scope | parsers:read |
| Rate limit | Read |
Example request
curl -sS https://api.parsedit.com/api/v1/parsers/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/fields \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response
{
"fields": [
{
"key": "invoice_number",
"label": "Invoice Details: Invoice Number",
"section": "Invoice Details",
"type": "string",
"field_type": "general"
},
{
"key": "line_items",
"label": "Line Items",
"section": "Line Items",
"type": "string",
"field_type": "line",
"children": [
{ "key": "description", "label": "Line Items: Description", "type": "string" },
{ "key": "amount", "label": "Line Items: Amount", "type": "string" }
]
}
]
}
Errors: 400, 401, 403, 404 parser not found.
GET /api/v1/documents
List documents for the authenticated workspace. Nested packet children are excluded from the list (parents and standalone documents only).
| Scope | documents:read |
| Rate limit | Read |
| Pagination | limit, cursor; next page via next_cursor in the JSON body |
| Query | Type | Description |
|---|---|---|
parser_id | uuid | Filter to one parser |
status | enum | One of: intake, queued, pending, processing, completed, cancelled, draft, approved, rejected, failed, failed_mapping, unrouted |
include_extracted | true / false / 1 / 0 | When true, each document includes extracted_data |
limit | integer | 1–100 (default 50) |
cursor | string | Opaque cursor from prior next_cursor |
Example request
curl -sS "https://api.parsedit.com/api/v1/documents?parser_id=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&status=completed&limit=20" \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response
{
"documents": [
{
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"file_name": "invoice-1042.pdf",
"status": "completed",
"created_at": "2026-03-12T15:01:00.000Z",
"updated_at": "2026-03-12T15:02:10.000Z",
"source": "webhook_inbound",
"packet_role": "standalone",
"parent_document_id": null,
"page_start": null,
"page_end": null,
"routing_status": "not_applicable",
"suggested_document_type": null
}
],
"next_cursor": null
}
Errors: 400 invalid query/cursor, 401, 403.
POST /api/v1/documents
Create (ingest) a document for a parser. Same format/size limits and duplicate detection as other intake channels (up to 50 MB per file).
| Scope | documents:write |
| Rate limit | Write |
| Success | 201 |
Provide the file in one of three ways.
Multipart form upload
Content-Type: multipart/form-data
| Field | Required | Description |
|---|---|---|
parser_id | Yes | Target parser UUID |
file | Yes | File part |
curl -sS -X POST https://api.parsedit.com/api/v1/documents \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -F "parser_id=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" \ -F "file=@./invoice-1042.pdf"
JSON with base64
Content-Type: application/json
| Field | Required | Description |
|---|---|---|
parser_id | Yes | Target parser UUID |
file_base64 | Yes* | Base64-encoded bytes |
file_name | No | Defaults to upload.bin |
content_type | No | MIME type |
JSON with public URL
| Field | Required | Description |
|---|---|---|
parser_id | Yes | Target parser UUID |
file_url | Yes* | Public HTTPS URL Parsedit fetches |
file_name | No | Defaults from URL path when possible (falls back to document.pdf) |
content_type | No | MIME type override |
curl -sS -X POST https://api.parsedit.com/api/v1/documents \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"file_url": "https://example.com/files/invoice-1042.pdf",
"file_name": "invoice-1042.pdf"
}'
Example response (201)
{
"document": {
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"account_id": "11111111-1111-1111-1111-111111111111",
"file_name": "invoice-1042.pdf",
"status": "queued",
"created_at": "2026-03-12T15:01:00.000Z",
"updated_at": "2026-03-12T15:01:00.000Z",
"source": "webhook_inbound"
}
}
Exact document fields may include additional metadata used by the product; treat id, parser_id, file_name, status, and timestamps as the stable core.
Errors
| Status | Example error |
|---|---|
400 | parser_id and file are required, parser_id is required, Provide file_base64, file_url, or multipart file, file_url must be a public HTTPS URL, Failed to fetch file URL (…), |
401 / 403 | Auth / scope |
404 | Parser not found |
413 | File exceeds 50 MB limit |
GET /api/v1/documents/:id
Get a single document by id.
| Scope | documents:read |
| Rate limit | Read |
Example request
curl -sS https://api.parsedit.com/api/v1/documents/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response
{
"document": {
"id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"file_name": "invoice-1042.pdf",
"status": "completed",
"created_at": "2026-03-12T15:01:00.000Z",
"updated_at": "2026-03-12T15:02:10.000Z",
"source": "webhook_inbound",
"error_message": null
}
}
Errors: 400, 401, 403, 404 { "error": "Document not found" }.
GET /api/v1/documents/:id/result
Get the extracted result for a document.
| Scope | documents:read |
| Rate limit | Read |
| Query | Description |
|---|---|
format=flat | Returns destination-style template scalars suitable for Zapier/Make field mapping (recommended for integrations). Omitting format returns the stored extracted_data blob. |
Example request (flat)
curl -sS "https://api.parsedit.com/api/v1/documents/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/result?format=flat" \ -H "Authorization: Bearer $ACCESS_TOKEN"
Example response (format=flat)
{
"document_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"file_name": "invoice-1042.pdf",
"status": "completed",
"extracted_data": {
"invoice_number": "INV-1042",
"vendor_name": "Acme Supplies",
"total_amount": "1250.00",
"line_items": [
{ "description": "Paper reams", "amount": "250.00" },
{ "description": "Toner", "amount": "1000.00" }
]
},
"error_message": null,
"format": "flat"
}
Example response (default / raw)
{
"document_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"file_name": "invoice-1042.pdf",
"status": "completed",
"extracted_data": {},
"error_message": null
}
Keys inside extracted_data follow the parser template. Use GET /api/v1/parsers/:id/fields for the mapping catalog.
Errors: 400, 401, 403, 404 document not found.
POST /api/v1/webhooks
Subscribe to integration webhook events. Returns a signing secret once — store it securely.
| Scope | webhooks:write |
| Rate limit | Write |
| Success | 201 |
| Body field | Type | Required | Description |
|---|---|---|---|
event | string | Yes | document.received, document.parsed, or document.failed |
target_url | string | Yes | Public HTTPS URL that receives POSTs |
parser_id | uuid | No | When set, only events for that parser are delivered; omit for account-wide |
Example request
curl -sS -X POST https://api.parsedit.com/api/v1/webhooks \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event": "document.parsed",
"target_url": "https://hooks.example.com/parsedit",
"parser_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
}'
Example response (201)
{
"id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"secret": "whsec_…"
}
Errors: 400 validation / failed create, 401, 403, 404 if parser_id is not in the workspace.
These API subscriptions are separate from the per-parser webhook destination.
DELETE /api/v1/webhooks/:id
Delete a webhook subscription.
| Scope | webhooks:write |
| Rate limit | Write |
| Success | 204 empty body |
Example request
curl -sS -X DELETE https://api.parsedit.com/api/v1/webhooks/cccccccc-cccc-cccc-cccc-cccccccccccc \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-o /dev/null -w "%{http_code}\n"
Errors: 400 failed delete / missing id, 401, 403.
Webhook delivery
When a subscribed event occurs, Parsedit POSTs a JSON body to target_url.
Events
| Event | When |
|---|---|
document.received | A new document was ingested into a parser (for example via this API, email, or Zapier upload) |
document.parsed | Extraction finished; payload includes flat extractedData aligned to the parser template |
document.failed | Supported subscription event for failed processing; payload may include errorMessage |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-Parsedit-Event | Event name (for example document.parsed) |
X-Parsedit-Signature | Hex-encoded HMAC-SHA256 of the raw request body, keyed with the subscription secret |
Deliveries time out after 10 seconds. Non-2xx responses are retried with backoff (up to several attempts) before the delivery is marked dead.
Payload shape
Field names are camelCase.
{
"event": "document.parsed",
"accountId": "11111111-1111-1111-1111-111111111111",
"parserId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"documentId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"fileName": "invoice-1042.pdf",
"status": "completed",
"extractedData": {
"invoice_number": "INV-1042",
"vendor_name": "Acme Supplies",
"total_amount": "1250.00",
"line_items": [
{ "description": "Paper reams", "amount": "250.00" }
]
},
"occurredAt": "2026-03-12T15:02:10.000Z"
}
| Field | Always | Notes |
|---|---|---|
event | Yes | Same as X-Parsedit-Event |
accountId | Yes | Workspace id |
parserId | Yes | Parser id |
documentId | Yes | Document id |
occurredAt | Yes | ISO-8601 timestamp |
fileName | Often | Original file name |
status | Often | Document status at emit time |
extractedData | On document.parsed | Flat template scalars (same projection as ?format=flat) |
errorMessage | On failures | Present when a failure message is available |
document.received payloads typically omit extractedData.
Verifying signatures
- Read the raw request body bytes (do not re-serialize JSON before verifying).
- Compute
HMAC-SHA256(secret, rawBody)and hex-encode the digest. - Compare to
X-Parsedit-Signatureusing a constant-time equality check. - Optionally assert
X-Parsedit-Eventmatches an event you expect.
Node.js sketch
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyParseditSignature(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signatureHeader ?? '', 'utf8');
return a.length === b.length && timingSafeEqual(a, b);
}
Quick reference
| Method | Path | Scope |
|---|---|---|
GET | /api/v1/me | account:read |
GET | /api/v1/parsers | parsers:read |
GET | /api/v1/parsers/:id | parsers:read |
GET | /api/v1/parsers/:id/fields | parsers:read |
GET | /api/v1/documents | documents:read |
POST | /api/v1/documents | documents:write |
GET | /api/v1/documents/:id | documents:read |
GET | /api/v1/documents/:id/result | documents:read |
POST | /api/v1/webhooks | webhooks:write |
DELETE | /api/v1/webhooks/:id | webhooks:write |
Related
- Zapier — OAuth Zapier app overview
- Document intake — non-API intake channels
- Webhooks destination — per-parser delivery with custom headers