# CV Extract V2 (legacy)

> Reference for the legacy asynchronous V2 resume parser. Kept for existing integrations; use V3 for new ones.

> **Legacy.** Maintained for existing integrations only.

Source: https://onlineresumeparser.com/api-docs/extract-v2 · Markdown: https://onlineresumeparser.com/api-docs/extract-v2.md

> **Warning: Legacy contract.** V2 is maintained for existing integrations only. [HireLayer CV Extract V3](https://onlineresumeparser.com/api-docs/extract.md) is synchronous, supports more formats and returns typed errors.

## Submit a resume (legacy V2)

`POST https://onlineresumeparser.com/api/v2/parser`

Legacy asynchronous contract: the request is accepted with `202` and the result is posted to `webhook_url`.

- **Authentication:** `X-API-Key` header
- **Content type:** `multipart/form-data`
- **Billing:** 1 credit when the request is accepted (HTTP 202)
- **Client timeout:** at least 50 seconds
- **Status:** Legacy — maintained for existing integrations only

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | `file` | Yes | The resume: PDF, DOCX, ODT, PPTX, ODP, TXT, JPG or PNG. |
| `webhook_url` | `string (URL)` | Yes | URL that receives the result when processing ends. |
| `application_id` | `string` | No | Your own reference, echoed in the webhook payload. |

### Example request

**cURL**

```bash
curl -X POST https://onlineresumeparser.com/api/v2/parser \
  -H "X-API-Key: $HIRELAYER_API_KEY" \
  -F "file=@resume.pdf;type=application/pdf" \
  -F "webhook_url=https://example.com/hirelayer/webhook" \
  -F "application_id=app_123"
```

**Python**

```python
import os

import requests

with open("resume.pdf", "rb") as file:
    response = requests.post(
        "https://onlineresumeparser.com/api/v2/parser",
        headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
        files={"file": ("resume.pdf", file, "application/pdf")},
        data={
            "webhook_url": "https://example.com/hirelayer/webhook",
            "application_id": "app_123",
        },
        timeout=50,
    )
response.raise_for_status()

data = response.json()
print(data["message"])
```

**TypeScript**

```typescript
import { readFile } from 'node:fs/promises'

const form = new FormData()
form.append(
  'file',
  new Blob([await readFile('resume.pdf')], { type: 'application/pdf' }),
  'resume.pdf'
)
form.append('webhook_url', 'https://example.com/hirelayer/webhook')
form.append('application_id', 'app_123')

const response = await fetch('https://onlineresumeparser.com/api/v2/parser', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  body: form,
  signal: AbortSignal.timeout(50_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.message)
```

### Response `202`

Accepted. The result is sent to `webhook_url` later.

| Field | Type | Description |
| --- | --- | --- |
| `message` | `string` | Value: `"CV processing initiated."`. |
| `body` | `null` |  |
| `error` | `boolean` | Value: `false`. |

```json
{
  "message": "CV processing initiated.",
  "body": null,
  "error": false
}
```

### Errors

| Status | `error` | `code` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | `An error occurred while processing the document or extracting its text. Please try again later.` | — | Any error from the legacy parser is returned with its status code (`400` for a missing file or `webhook_url`, unsupported type…) and this generic message. | Do not retry |
| `401` | `Missing API Key` | — | The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. | Do not retry |
| `401` | `Invalid API Key` | — | The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. | Do not retry |
| `403` | `Insufficient credits available` | — | The account has no credit left. | Fix, then retry |
| `415` | `Content-Type must be multipart/form-data` | — | The request is not `multipart/form-data`. | Do not retry |
| `503` | `Credit service temporarily unavailable` | — | Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Headers: `Retry-After: 1`. | Retry with backoff |

Example error (`401`):

```json
{
  "error": "Missing API Key"
}
```

### Behavior

- **Asynchronous:** The gateway waits up to 45 seconds for the request to be accepted. The webhook then receives `{"status": "success", …}` with the parsed resume, or `{"status": "failure", "error_code": …, "error_message": …}`.
- **Billing:** The credit is consumed when the request is accepted, even if processing fails later.

## Next

- [HireLayer CV Extract](https://onlineresumeparser.com/api-docs/extract.md): Parse a resume file into structured candidate JSON with one multipart request: contact details, experience, education, languages and typed skills.
