# Contacts API for agents

> API for Agents · https://apiforagents.dev/platforms/contacts · HTML twin: https://apiforagents.dev/platforms/contacts

A person's work email and mobile number, by LinkedIn handle.

Endpoint: `POST https://apiforagents.dev/v1/contacts`. One op per call. Quote first with `"estimateOnly": true`.

## Prices

Priced per request; the capabilities endpoint prints the rate and every call can be quoted first.

## Operations

### reveal

Work email and mobile number for up to ten people, by LinkedIn /in/ handle or profile URL. A reveal is the BUYER'S asset: billed per person and per part, owned by your org, never shared with another org. A part you already own is free; only the missing part is bought.

| Field | Required | Type | Meaning |
|---|---|---|---|
| subjects | yes | list of string (1-10 items) | /in/<slug> handles or profile URLs. |
| reveal | no | list of enum | Which parts to reveal. Both by default. A contact is an email AND a number; a phone is five emails' worth, so ask ["email"] when unneeded. Values: email, phone. |
| verify | no | boolean | Verify each email found before it counts: an address a verifier calls invalid is kept as a fact but does not satisfy the ask, and the next source is tried. A verdict costs a fraction of a cent per address. |
| tryAnotherSource | no | boolean | "What I hold is wrong. Get me another." EVERY part in reveal that your org holds is marked wrong and bought again from a source other than the one that gave it, at that part's price. So list only the part that is wrong. One call is one step: the next source's answer is returned even when it is the same value (flagged); ask again to go one source further. Marking is free and final. A part you do not hold is simply revealed. |

Response fields (each row of data; every field present, null when the source had nothing):

| Field | Type |
|---|---|
| publicId | string |
| profileUrl | string |
| name | string |
| title | string |
| company | string |
| emails | list of object |
| phones | list of object |
| firstName | string |
| lastName | string |
| headline | string |
| location | object |
| currentCompany | object |
| experience | list of object |
| education | list of object |
| skills | list of string |
| languages | list of object |
| linkedinUrl | string |
| details | object |

`details` is always the last field: everything else the source sent for that row, under our own field names. Its keys depend on the source that answered, so read them defensively; it is `{}` when there is nothing more.

Example request:

```bash
curl -X POST https://apiforagents.dev/v1/contacts \
  -H "Authorization: Bearer $AFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"op":"reveal","subjects":["satyanadella"],"reveal":["email"]}'
```

Example response rows (made-up names, real field names; a live row carries every field above):

```json
[
  {
    "publicId": "ananya-rao",
    "profileUrl": "https://www.linkedin.com/in/ananya-rao",
    "emails": [
      {
        "value": "ananya.rao@paylane.example",
        "type": "work",
        "status": "verified"
      }
    ],
    "phones": [
      {
        "value": "+91 98450 00000",
        "type": "mobile"
      }
    ],
    "details": {}
  },
  {
    "publicId": "marcus-lee",
    "profileUrl": "https://www.linkedin.com/in/marcus-lee",
    "emails": [
      {
        "value": "marcus@northfold.example",
        "type": "work",
        "status": "verified"
      }
    ],
    "phones": [
      {
        "value": "+44 7700 900123",
        "type": "mobile"
      }
    ],
    "details": {}
  }
]
```

Try it in the playground: https://apiforagents.dev/dashboard/run?platform=contacts&op=reveal (sign in first).

## Notes for the agent

A person's work email and mobile number, by LinkedIn handle. And the two follow-ups people ask for: • "This number / email is wrong, give me another" → reveal again with tryAnotherSource=true and reveal=[that part ONLY]. Every part listed is marked wrong and bought again from a DIFFERENT source, at that part's price. So a wrong number with reveal=["email","phone"] also throws away a good email. One call is one step: if the next source gives the same value back it is shown flagged and the walk stops (say so again to go one source further); a free miss moves on by itself. • "Give me a verified email" → reveal with verify=true. Every address is checked by a verifier before it counts (status "valid" / "invalid" / "risky"); an invalid one does not satisfy the ask and the next source is tried. A fraction of a cent per address. If no verifier can answer, the address comes back "unverified" and a note says so. One op, reveal: up to ten people by /in/ handle or profile URL; reveal=["email","phone"] by default, ["email"] alone when the number is not needed (a phone costs about five emails' worth). Every answer carries a `trail`. The sources walked, in order, with what each found and cost; a point's `source` is a label within that answer ("1", "2"), never a name. The /in/ handle must come from EVIDENCE: web-search the person, or find them with linkedin searchPeople, and use the /in/ you SAW. A handle typed from a name is charged for and can reveal a DIFFERENT person's number; a member id (ACoAA…/ACwAA…) is not a handle and is refused unspent. A reveal is YOUR org's asset: a part you already own is free, a part with nothing on file is answered once and never bought again, and nothing revealed is ever shared with another org. Batch subjects inside one call; estimate_only=true quotes without spending.
