> ## Documentation Index
> Fetch the complete documentation index at: https://powerlead.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Retrieve Bulk Enrichment Job Results

## GET `/public/v1/bulk/result/{job_id}`

Use this endpoint to retrieve the final results of a completed bulk enrichment job. The response contains the enriched contact data, including emails and phone numbers (depending on the requested return fields).

***

### Authorization

**Header:**\
`X-API-Key: <your_api_key>`

API key must be generated from your account at [dashboard.powerlead.com](https://dashboard.powerlead.com).

***

### Path Parameters

* `job_id` (string) **required**\
  The unique identifier of the bulk enrichment job whose results you wish to retrieve.

***

### Success Response

**HTTP 200 OK**

```json theme={null}
[
  {
    "Linkedin URL": "https://linkedin.com/in/example",
    "Full name": "Jane Doe",
    "First name": "Jane",
    "Last name": "Doe",
    "Email": "jane.doe@example.com",
    "Company name": "Example Inc.",
    "Company domain": "example.com",
    "emails": ["jane.doe@example.com", "contact@example.com"],
    "phones": ["+1234567890", "+1987654321"]
  }
]
```

**Field Descriptions:**

* Original input fields are echoed back (e.g., name, company, LinkedIn URL).
* `emails`: Array of enriched email addresses.
* `phones`: Array of enriched phone numbers.

***

### Error Responses

**HTTP 422 Unprocessable Entity** – Invalid or malformed `job_id`.

```json theme={null}
{
  "detail": [
    {
      "loc": ["path", "job_id"],
      "msg": "Invalid job ID format",
      "type": "value_error"
    }
  ]
}
```

***

### Notes

* This endpoint returns results only for jobs with a `completed` status.
* Use the [`GET /public/v1/bulk/status/{job_id}`](#) endpoint to check if the job is finished before requesting results.
* Results are available for at least **30 days** after completion.

***

## Webhook Result Delivery (Optional)

Instead of polling this endpoint, you can opt in to receive results automatically via a webhook when starting a bulk job.

### How to Enable Webhook Delivery

Include a `webhook` block in your job creation request (`POST /public/v1/bulk/start`):

```json theme={null}
"webhook": {
  "url": "https://your.app.local/api/webhook",
  "timeout_sec": 5,
  "max_retries": 5,
  "secret": "u5Y0v6g8O8i2b9Uq2xF3m7QmS1hI3yZqW9tK0dV2pE8",
  "headers": {
    "X-API-Key": "client-local-check"
  }
}
```

### Webhook Payload Example

```json theme={null}
{
  "event": "csv_enrichment.completed",
  "delivery_id": "1a2b3c-...",
  "job_uuid": "b3b2d4-...",
  "status": "DONE",  // or DONE_PARTIALLY / ERROR
  "created_at": "2025-08-12T14:26:57Z",
  "updated_at": "2025-08-12T14:31:12Z",
  "done_at": "2025-08-12T14:31:12Z",
  "results": [ ... ]
}
```

### Webhook Headers

* `Content-Type: application/json`
* `User-Agent: CSVEnrichmentWebhook/1.0`
* `X-Delivery-Id: <uuid>` – Unique delivery ID (idempotency)
* `X-Webhook-Timestamp: <unix_seconds>` – UTC timestamp of delivery
* `X-Webhook-Signature: v1=<hex>` – HMAC-SHA256 if `secret` is provided

### Signature Verification (Optional but Recommended)

If you use the `secret` field, each payload will be signed for authenticity.

**Signature Algorithm:**

```
signature = hex(HMAC_SHA256(secret, "{timestamp}.{raw_body_bytes}"))
```

**Sample Verification (Python / FastAPI):**

```python theme={null}
from fastapi import FastAPI, Request, Header, HTTPException
import hmac, hashlib, time

app = FastAPI()
SHARED_SECRET = "your-secret-here"
MAX_SKEW = 300  # 5 minutes

def verify(ts: str, sig: str, body: bytes) -> bool:
    if abs(time.time() - int(ts)) > MAX_SKEW:
        return False
    mac = hmac.new(SHARED_SECRET.encode(), (ts + ".").encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig.removeprefix("v1="), mac)

@app.post("/webhook")
async def webhook(
    request: Request,
    x_webhook_timestamp: str = Header(...),
    x_delivery_id: str = Header(...),
    x_webhook_signature: str | None = Header(None)
):
    body = await request.body()
    if SHARED_SECRET:
        if not x_webhook_signature or not verify(x_webhook_timestamp, x_webhook_signature, body):
            raise HTTPException(400, "bad signature")
    return {"ok": True}
```

***

### Delivery Behavior

* Any `2xx` HTTP status is considered successful.
* Retries happen on `4xx`, `5xx`, timeouts, or network errors.
* Retry delay is exponential (1 minute, 2 minutes, 4 minutes, etc.), up to `max_retries`.
* Internal retry window capped to 1 hour.
* All attempts and responses are logged internally.

***

### Recommendations for Receivers

* Respond quickly (≤2s). Handle processing asynchronously.
* Validate `X-Webhook-Signature` and `X-Webhook-Timestamp` if using a secret.
* Use `X-Delivery-Id` to ensure idempotency and prevent duplicate processing.
* Secure your endpoint with custom headers (e.g. `X-API-Key`).
* Validate that the timestamp is within a reasonable range (e.g., ±5 minutes).
