# Profile live

> Trigger a live fetch of a Social profile and retrieve results via polling or webhook callback.

## Introduction

The Person Profile Live endpoint triggers a real-time fetch of a Social profile. Instead of relying on cached data, it pulls a fresh copy and returns the result asynchronously.

Use this endpoint when you need the most up-to-date profile data and can wait for the async result. Retrieve the result via [polling](https://docs.reversecontact.com/guides/polling) (recommended, no public endpoint required) or via a [webhook callback](https://docs.reversecontact.com/guides/webhooks) (advanced, push delivery).

> [!NOTE]
> **Async, not real-time.** We aim to deliver each result in under 15 seconds, but it can take tens of minutes depending on conditions. Built to update databases and feed async pipelines, not to back a synchronous request a user is waiting on. See [Polling](https://docs.reversecontact.com/guides/polling) for delivery details.

> [!IMPORTANT]
> **Not available on the free trial.** This endpoint requires an active pay-as-you-go balance or an Enterprise plan. [Add credits](https://app.reversecontact.com/settings/billing) to your workspace to unlock it, or contact sales to discuss Enterprise pricing.

## Authorization

| Name     | Type   | Required | Description                                                                                         |
| -------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `apikey` | string | Yes      | Your API key from the developer dashboard. Pass it via `Authorization: Bearer YOUR_API_KEY` header. |

## Request body

| Name         | Type   | Required    | Description                                                                                         |
| ------------ | ------ | ----------- | --------------------------------------------------------------------------------------------------- |
| `url`        | string | Conditional | Public Social profile URL (e.g. `https://social.com/in/janedoe`). Provide exactly one of `url` or `id`. |
| `id`         | string | Conditional | ReverseContact person id (`prs_…`). The gateway resolves it to the current Social public slug before starting the scrape. |
| `webhookUrl` | string | Optional | HTTPS push URL for webhook delivery. Omit this field to retrieve the result via [polling](https://docs.reversecontact.com/guides/polling) (recommended). See the [Webhooks guide](https://docs.reversecontact.com/guides/webhooks) when you need push delivery. |

## Response structure

This endpoint replies immediately with a `webhookId`. You then have two ways to retrieve the actual result:

1. **Polling (recommended).** Call `GET /v2/webhooks/:webhookId` until the status is `succeeded`. No webhook URL needed. See the [Polling guide](https://docs.reversecontact.com/guides/polling).
2. **Webhook callback (advanced).** If you provided a `webhookUrl` (or a workspace default is configured), the gateway POSTs the person data to that URL once the fetch completes. See [Webhook callback payload (advanced)](#webhook-callback-payload-advanced) below.

| Field     | Type    | Description                                      |
| --------- | ------- | ------------------------------------------------ |
| `success` | boolean | `true` if the job was created successfully       |

**data** (JobAck): Initial job acknowledgement. Save the `webhookId` to retrieve the result via polling (`GET /v2/webhooks/:webhookId`) or to correlate an upcoming webhook callback (`null` on error responses).

| Field       | Type   | Description                                                                                                                  |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `status`    | string | Job status, always `"created"` for the initial response                                                                      |
| `webhookId` | string | Unique identifier (UUID). **Save it.** Use it to poll the result at `GET /v2/webhooks/:webhookId`, or to correlate an incoming webhook callback. |
| `pollUrl`   | string | Convenience hint pointing to the polling endpoint for this job (e.g. `/v2/webhooks/{webhookId}`).                            |

| Field   | Type   | Description              |
| ------- | ------ | ------------------------ |
| `error` | `null` | Always `null` on success |

**quotas** (V2QuotaInfo): Credits and rate limit usage after this request

| Field             | Type   | Description                      |
| ----------------- | ------ | -------------------------------- |
| `creditsConsumed` | number | Credits consumed by this request |

**workspace** (Workspace): Workspace-level limits

| Field                 | Type    | Description                                    |
| --------------------- | ------- | ---------------------------------------------- |
| `id`                  | string  | Your workspace identifier                      |
| `hasUnlimitedCredits` | boolean | Whether workspace credits are unlimited        |
| `allowDailyOvercost`  | boolean | Whether the workspace may exceed its daily cap |

**credits** (Credits): Credit balance

| Field   | Type   | Description                |
| ------- | ------ | -------------------------- |
| `total` | number | Total credits in your plan |
| `used`  | number | Credits consumed so far    |
| `left`  | number | Remaining credits          |

**dailyLimit** (DailyLimit): Daily request limit (`null` if not configured)

| Field       | Type   | Description                             |
| ----------- | ------ | --------------------------------------- |
| `limit`     | number | Maximum requests per day                |
| `used`      | number | Requests made today                     |
| `left`      | number | Remaining requests today                |
| `nextReset` | string | When the daily window resets (ISO 8601) |

**minuteRateLimit** (MinuteRateLimit): Per-minute rate limit

| Field       | Type   | Description                              |
| ----------- | ------ | ---------------------------------------- |
| `limit`     | number | Maximum requests per minute              |
| `used`      | number | Requests made in the current minute      |
| `left`      | number | Remaining requests in the current minute |
| `nextReset` | string | When the minute window resets (ISO 8601) |

**key** (ApiKey): API key-level limits (`null` when request is from dashboard)

| Field | Type   | Description        |
| ----- | ------ | ------------------ |
| `id`  | string | API key identifier |

**dailyLimit** (KeyDailyLimit): Daily limit for this key (`null` if not configured)

| Field       | Type   | Description                             |
| ----------- | ------ | --------------------------------------- |
| `limit`     | number | Maximum requests per day                |
| `used`      | number | Requests made today                     |
| `left`      | number | Remaining requests today                |
| `nextReset` | string | When the daily window resets (ISO 8601) |

**minuteRateLimit** (KeyMinuteRateLimit): Minute rate limit for this key

| Field       | Type   | Description                              |
| ----------- | ------ | ---------------------------------------- |
| `limit`     | number | Maximum requests per minute              |
| `used`      | number | Requests made in the current minute      |
| `left`      | number | Remaining requests in the current minute |
| `nextReset` | string | When the minute window resets (ISO 8601) |

**metadata** (V2ResponseMetadata): Request tracking information

| Field             | Type   | Description                          |
| ----------------- | ------ | ------------------------------------ |
| `requestId`       | string | Unique identifier for this request   |
| `executionTimeMs` | number | Total execution time in milliseconds |

## Webhook callback payload (advanced)

Once the live fetch completes, the gateway POSTs a JSON payload to your `webhookUrl`. Use the `webhookId` from the initial response to correlate the callback with your original request.

The payload is **not wrapped** in an envelope (no `success`, `error`, `metadata`, or `quotas` keys at the top level, unlike the synchronous response).

| Field       | Type   | Description                                                                                                                         |
| ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `webhookId` | string | Always present. The same identifier returned in the initial response. Use it to correlate the callback with the original request. |

**data** (Profile): Always present. On success: the enriched person profile (fields below). On failure: see [Webhook callback errors (advanced)](#webhook-callback-errors-advanced).

| Field                  | Type           | Description                                            |
| ---------------------- | -------------- | ------------------------------------------------------ |
| `id`                   | string         | ReverseContact person id (`prs_...`). Use it as the `id` parameter of the [Person Profile endpoint](https://docs.reversecontact.com/endpoints/fetch-profile) to re-fetch this profile |
| `publicId`             | string         | Social public identifier (the `/in/xxx` slug)          |
| `memberId`             | string         | Social internal member ID                              |
| `linkedinUrl`          | string         | Full Social profile URL                                |
| `firstName`            | string \| null | First name                                             |
| `lastName`             | string \| null | Last name                                              |
| `headline`             | string \| null | Profile headline                                       |
| `summary`              | string \| null | About / summary section                                |
| `pronoun`              | string \| null | Preferred pronoun (e.g. `"she/her"`, `"he/him"`)       |
| `isOpenToWork`         | boolean        | Whether the person is open to work                     |
| `hasPremium`           | boolean        | Whether the person has Social Premium                  |
| `hasVerificationBadge` | boolean        | Whether the profile is verified                        |
| `isInRemembrance`      | boolean        | Whether the profile is memorialised                    |
| `photoUrl`             | string \| null | Profile photo URL                                      |
| `backgroundUrl`        | string \| null | Background / banner image URL                          |
| `creationDate`         | string \| null | Account creation date (ISO 8601)                       |
| `followersCount`       | number \| null | Number of followers                                    |
| `connectionsCount`     | number \| null | Number of connections                                  |
| `skills`               | string[]       | List of skill names                                    |

**location** (ProfileLocation): Geographic location

| Field         | Type           | Description                    |
| ------------- | -------------- | ------------------------------ |
| `city`        | string \| null | City name                      |
| `state`       | string \| null | State or region                |
| `country`     | string \| null | Country name                   |
| `countryCode` | string \| null | ISO country code (e.g. `"US"`) |
| `rawLocation` | string \| null | Raw, unparsed location label   |

> [!NOTE]
> When a profile lists several jobs, the current position is selected (and the experience and education lists are ordered) by fixed, predictable rules. See [Current position and work history ordering](https://docs.reversecontact.com/guides/current-position-resolution).

**currentPosition** (ProfilePosition): Current job position

| Field               | Type              | Description                                      |
| ------------------- | ----------------- | ------------------------------------------------ |
| `title`             | string \| null    | Job title                                        |
| `description`      | string \| null    | Role description                                 |
| `contractType`      | string \| null    | Contract type (e.g. `"Permanent"`, `"Contract"`) |
| `companyName`       | string \| null    | Company name                                     |
| `companyLinkedinId` | string \| null    | Company Social identifier                        |
| `companyUrl`        | string \| null    | Company Social profile URL                       |
| `companyLocation`   | string \| null    | Company location (city, region, country)         |
| `companyLogoUrl`    | string \| null    | Company logo URL                                 |
| `startEndDate`      | DateRange \| null | Position duration                                |

**startEndDate** (DateRange): Period boundaries

| Field   | Type           | Description                            |
| ------- | -------------- | -------------------------------------- |
| `start` | string \| null | Start date (ISO 8601)                  |
| `end`   | string \| null | End date (ISO 8601, `null` if ongoing) |

**experience** (ProfileExperience[]): Professional experiences

| Field               | Type              | Description                                      |
| ------------------- | ----------------- | ------------------------------------------------ |
| `title`             | string \| null    | Job title                                        |
| `description`       | string \| null    | Role description                                 |
| `contractType`      | string \| null    | Contract type (e.g. `"Permanent"`, `"Contract"`) |
| `companyName`       | string \| null    | Company name                                     |
| `companyLinkedinId` | string \| null    | Company Social identifier                        |
| `companyUrl`        | string \| null    | Company Social profile URL                       |
| `companyLocation`   | string \| null    | Company location (city, region, country)         |
| `companyLogoUrl`    | string \| null    | Company logo URL                                 |
| `startEndDate`      | DateRange \| null | Position duration                                |

**startEndDate** (DateRange): Period boundaries

| Field   | Type           | Description                            |
| ------- | -------------- | -------------------------------------- |
| `start` | string \| null | Start date (ISO 8601)                  |
| `end`   | string \| null | End date (ISO 8601, `null` if ongoing) |

**education** (ProfileEducation[]): Education history

| Field           | Type              | Description                                |
| --------------- | ----------------- | ------------------------------------------ |
| `degreeName`    | string \| null    | Degree name (e.g. `"B.S."`, `"MBA"`)       |
| `fieldOfStudy`  | string \| null    | Field of study                             |
| `description`   | string \| null    | Additional description                     |
| `grade`         | string \| null    | Grade or honors                            |
| `schoolName`    | string \| null    | School name                                |
| `schoolUrl`     | string \| null    | School Social profile URL                  |
| `schoolLogoUrl` | string \| null    | School logo URL                            |
| `startEndDate`  | DateRange \| null | Education period                           |

**startEndDate** (DateRange): Period boundaries

| Field   | Type           | Description                            |
| ------- | -------------- | -------------------------------------- |
| `start` | string \| null | Start date (ISO 8601)                  |
| `end`   | string \| null | End date (ISO 8601, `null` if ongoing) |

**languages** (ProfileLanguage[]): Spoken languages

| Field         | Type           | Description                                                            |
| ------------- | -------------- | ---------------------------------------------------------------------- |
| `language`    | string \| null | Language name                                                          |
| `proficiency` | string \| null | Proficiency level (e.g. `"Native or bilingual"`, `"Full professional"`) |

**recommendations** (ProfileRecommendation[]): Received recommendations

| Field            | Type           | Description                             |
| ---------------- | -------------- | --------------------------------------- |
| `caption`        | string \| null | Short caption / role of the recommender |
| `description`    | string \| null | Recommendation text                     |
| `authorFullname` | string \| null | Full name of the recommender            |
| `authorUrl`      | string \| null | Recommender Social profile URL          |

**certifications** (ProfileCertification[]): Professional certifications

| Field              | Type           | Description                                       |
| ------------------ | -------------- | ------------------------------------------------- |
| `name`             | string \| null | Certification name                                |
| `organizationName` | string \| null | Issuing organization name                         |
| `organizationUrl`  | string \| null | Issuing organization profile URL                  |
| `issuedDate`       | string \| null | Issue date (ISO 8601, may be `null` if unknown)   |

**testScores** (ProfileTestScore[]): Standardized test scores

| Field       | Type           | Description           |
| ----------- | -------------- | --------------------- |
| `testTitle` | string \| null | Test name             |
| `score`     | string \| null | Score value           |
| `date`      | string \| null | Date taken (ISO 8601) |

## Webhook callback errors (advanced)

When the live fetch fails, `data` does **not** contain the profile fields. Instead, it contains the original input echoed back together with an `errorCode` identifying the failure reason:

**Error payload**

```json [Webhook callback (error)]
{
  "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "data": {
    "url": "https://www.social.com/in/janedoe",
    "errorCode": "data-not-found"
  }
}
```

`data.errorCode` will be one of the following values:

| Code               | Billed | Description                                                         |
| ------------------ | ------ | ------------------------------------------------------------------- |
| `person-not-found` | Yes    | The person profile doesn't exist or could not be scraped            |
| `invalid-data`     | No     | The fetched data failed quality validation                          |
| `fetch-data-error` | No     | The API failed while fetching data. Safe to retry                   |
| `result-unavailable` | No   | The enrichment completed but the result was lost on our side. Retry the job. If the request was charged, contact support to get the credit back. |

> [!NOTE]
> **Billed** means the credits consumed at request time are kept. **Not billed** means the credits are fully refunded to your workspace balance.

## Code examples

**Code Examples**

```javascript [JavaScript - Polling (recommended)]
const KEY = "YOUR_API_KEY";
const BASE = "https://api.reversecontact.com";

const trigger = await fetch(`${BASE}/v2/fetch/persons/live`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ url: "https://social.com/in/janedoe" })
});
const { data: { webhookId } } = await trigger.json();

async function pollUntilDone(id) {
  while (true) {
    const res = await fetch(`${BASE}/v2/webhooks/${id}`, {
      headers: { "Authorization": `Bearer ${KEY}` }
    });
    const { data } = await res.json();
    if (data.status === "succeeded") return data.result;
    if (data.status === "errored")   throw new Error(data.errorCode ?? "Job failed");
    await new Promise(r => setTimeout(r, 2000));
  }
}

const result = await pollUntilDone(webhookId);
console.log(result);
```
```javascript [JavaScript - Webhook]
const response = await fetch("https://api.reversecontact.com/v2/fetch/persons/live", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    url: "https://social.com/in/janedoe",
    webhookUrl: "https://your-app.com/webhooks/reversecontact"
  })
});

const result = await response.json();
console.log(result.data.status, result.data.webhookId);
```
```bash [cURL - Polling (recommended)]
KEY="YOUR_API_KEY"
ID=$(curl -sX POST https://api.reversecontact.com/v2/fetch/persons/live \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://social.com/in/janedoe"}' | jq -r .data.webhookId)

while :; do
  S=$(curl -s https://api.reversecontact.com/v2/webhooks/$ID \
    -H "Authorization: Bearer $KEY" | jq -r .data.status)
  [ "$S" = "succeeded" ] && break
  [ "$S" = "errored" ] && echo "job failed" && exit 1
  sleep 2
done

curl -s https://api.reversecontact.com/v2/webhooks/$ID \
  -H "Authorization: Bearer $KEY" | jq .data.result
```
```bash [cURL - Webhook]
curl -X POST https://api.reversecontact.com/v2/fetch/persons/live \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://social.com/in/janedoe",
    "webhookUrl": "https://your-app.com/webhooks/reversecontact"
  }'
```
```python [Python - Polling (recommended)]
import time
import requests

KEY  = "YOUR_API_KEY"
BASE = "https://api.reversecontact.com"
H    = {"Authorization": f"Bearer {KEY}"}

trigger = requests.post(
    f"{BASE}/v2/fetch/persons/live",
    headers={**H, "Content-Type": "application/json"},
    json={"url": "https://social.com/in/janedoe"}
)
webhook_id = trigger.json()["data"]["webhookId"]

def poll_until_done(wid):
    while True:
        data = requests.get(f"{BASE}/v2/webhooks/{wid}", headers=H).json()["data"]
        if data["status"] == "succeeded":
            return data["result"]
        if data["status"] == "errored":
            raise RuntimeError(data.get("errorCode") or "Job failed")
        time.sleep(2)

result = poll_until_done(webhook_id)
print(result)
```
```python [Python - Webhook]
import requests

response = requests.post(
    "https://api.reversecontact.com/v2/fetch/persons/live",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json"
    },
    json={
        "url": "https://social.com/in/janedoe",
        "webhookUrl": "https://your-app.com/webhooks/reversecontact"
    }
)

result = response.json()
print(result["data"]["status"], result["data"]["webhookId"])
```

## Example response

**Example Response**

```json [Initial response]
{
  "success": true,
  "data": {
    "status": "created",
    "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "pollUrl": "/v2/webhooks/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
  },
  "error": null,
  "metadata": {
    "requestId": "req_a1b2c3d4e5f6",
    "executionTimeMs": 120
  },
  "quotas": {
    "creditsConsumed": 2,
    "workspace": {
      "id": "ws_abc123",
      "credits": {
        "total": 10000,
        "used": 4523,
        "left": 5477
      },
      "dailyLimit": null,
      "minuteRateLimit": {
        "limit": 60,
        "used": 12,
        "left": 48,
        "nextReset": "2025-01-15T09:31:00.000Z"
      },
      "hasUnlimitedCredits": false,
      "allowDailyOvercost": false
    },
    "key": {
      "id": "key_abc123",
      "dailyLimit": null,
      "minuteRateLimit": {
        "limit": 60,
        "used": 12,
        "left": 48,
        "nextReset": "2025-01-15T09:31:00.000Z"
      }
    }
  }
}
```
```json [Webhook callback (success)]
{
  "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "data": {
    "id": "prs_01jbd5vewyenebxgaz869ffe5t",
    "publicId": "janedoe",
    "memberId": "286401114",
    "linkedinUrl": "https://www.social.com/in/janedoe",
    "firstName": "Jane",
    "lastName": "Doe",
    "headline": "VP of Engineering @ Acme Corp",
    "summary": "Backend leader specialized in TypeScript, DDD and Clean Architecture...",
    "pronoun": null,
    "isOpenToWork": false,
    "hasPremium": false,
    "hasVerificationBadge": false,
    "isInRemembrance": false,
    "photoUrl": "https://media.licdn.com/dms/image/.../profile.jpg",
    "backgroundUrl": "https://media.licdn.com/dms/image/.../background.jpg",
    "creationDate": "2013-09-01T12:00:00.000Z",
    "followersCount": 666,
    "connectionsCount": 661,
    "skills": [
      "TypeScript",
      "Domain-Driven Design (DDD)",
      "Node.js",
      "PostgreSQL"
    ],
    "location": {
      "city": "Paris",
      "state": "Île-de-France",
      "country": "France",
      "countryCode": "FR",
      "rawLocation": "Paris, Île-de-France, France"
    },
    "currentPosition": {
      "title": "VP of Engineering",
      "description": "- Leading engineering during a growth phase...",
      "contractType": null,
      "companyName": "Acme Corp",
      "companyLinkedinId": "89181966",
      "companyUrl": "https://www.social.com/company/acme-corp",
      "companyLocation": "Paris",
      "companyLogoUrl": "https://media.licdn.com/dms/image/.../logo.jpg",
      "startEndDate": {
        "start": "2026-01-01T00:00:00.000Z",
        "end": null
      }
    },
    "experience": [
      {
        "title": "VP of Engineering",
        "description": "- Leading engineering during a growth phase...",
        "contractType": "Permanent",
        "companyName": "Acme Corp",
        "companyLinkedinId": "89181966",
        "companyUrl": "https://www.social.com/company/acme-corp",
        "companyLocation": "Paris",
        "companyLogoUrl": "https://media.licdn.com/dms/image/.../logo.jpg",
        "startEndDate": {
          "start": "2026-01-01T00:00:00.000Z",
          "end": null
        }
      }
    ],
    "education": [
      {
        "degreeName": "Engineer's Degree in Computer Engineering",
        "fieldOfStudy": null,
        "description": "Generalist engineering degree focused on IT systems...",
        "grade": null,
        "schoolName": "ESILV",
        "schoolUrl": "https://www.social.com/school/esilvparis",
        "schoolLogoUrl": "https://media.licdn.com/dms/image/.../logo.jpg",
        "startEndDate": {
          "start": "2016-01-01T00:00:00.000Z",
          "end": "2019-12-31T00:00:00.000Z"
        }
      }
    ],
    "languages": [
      { "language": "English", "proficiency": "Full professional proficiency" },
      { "language": "Chinese", "proficiency": "Native or bilingual proficiency" }
    ],
    "recommendations": [
      {
        "authorFullname": "Karim Baali",
        "authorUrl": "https://www.social.com/in/karimbaali",
        "caption": "Co-founder @Fragments Studio",
        "description": "I had the opportunity to work with Jane, both at school and at work..."
      }
    ],
    "certifications": [
      {
        "name": "Test of English for International Communication",
        "organizationName": "ETS",
        "organizationUrl": "https://www.social.com/company/163705",
        "issuedDate": null
      }
    ],
    "testScores": []
  }
}
```
```json [Webhook callback (error)]
{
  "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "data": {
    "url": "https://www.social.com/in/janedoe",
    "errorCode": "data-not-found"
  }
}
```
