Fill columns
Send the people or companies you already have and name the columns you need. Every cell comes back with one answer, a confidence, and evidence.
POST /v1/datasetsA dataset is a table you seed. Each row names one person, company, or other entity; each column is a typed field such as headcount, socials.x, or
email.work. Ploid reads many sources for every cell, reconciles them into one answer, and returns that answer with a 0..1 confidence and the public pages it
came from. You pay only for cells that were found.
Use POST /v1/search or Sets to discover who belongs in a list. Use datasets when you already know the
rows and need their columns filled.
Requires an API key with the people:search scope. Older full-access keys that hold people:enrich and agent:chat also work.
Create a dataset
curl https://api.ploid.com/v1/datasets \
-H "Authorization: Bearer $PLOID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": { "type": "company" },
"rows": [
{ "domain": "linear.app" },
{ "name": "Ramp", "linkedin_url": "https://www.linkedin.com/company/ramp" }
],
"columns": [
{ "key": "description" },
{ "key": "headcount" },
{ "key": "headquarters" },
{ "key": "custom.pricing_model", "description": "How the company charges customers" }
]
}'| Field | Meaning |
|---|---|
entity.type | person, company, or custom. Decides which built-in columns are allowed. |
rows | 1 to 25 rows for sync requests, up to 2,000 with mode: "async". |
columns | 1 to 30 columns with unique keys. |
mode | sync (default) answers in the response. async returns 202 and fills rows in the background. |
deadline_ms | Sync only. 1,000 to 60,000; default 20,000. Cells still being read at the deadline return timeout. Ignored for async. |
freshness.max_age_days | 0 to 3,650. The oldest cached evidence reused without a live read; 0 re-reads everything. Omitted, each field uses its own window. |
include_unverified | Default false. When true, unverified cells also return their best value and evidence. They stay unverified and free. |
Rows
Every row needs at least one identifier: name, domain, linkedin_url, email, or a socials value. The more you send, the faster and more accurate the
match.
| Field | Meaning |
|---|---|
name | Person, company, or entity name. |
domain | The company's or person's own domain, such as linear.app. |
linkedin_url | A LinkedIn person (linkedin.com/in/…) or company (linkedin.com/company/…) URL. |
socials | Handles or profile URLs keyed by linkedin, x, instagram, youtube, tiktok, github, or website. |
email | A known email address. |
company | A person's employer, used to disambiguate common names. |
extra | Free-form string hints, echoed back on the row. |
Unknown row keys return 422.
Columns
A column is a built-in field for the entity type or a custom question.
| Entity | Built-in columns |
|---|---|
company | name, domain, description, founded_year, headquarters, headcount, industry, linkedin_url, page_text, socials.* |
person | name, title, company, location, email.work, email.personal, phone, website, socials.* |
custom | name, plus custom columns |
socials.* is any of socials.linkedin, socials.x, socials.instagram, socials.youtube, socials.tiktok, and socials.github.
Built-in columns have a fixed format: founded_year is a number, headcount a range such as "51–200", domain, linkedin_url, website, and socials.*
are URLs, and the rest are text. page_text returns the readable text of the company's site, truncated to max_chars (200 to 20,000; default 3,000).
A custom column is custom.<name>, where the name is 1 to 60 lowercase letters, digits, or underscores. It needs a description of what to find (up to 500
characters) and takes an optional format: text (default), number, date, boolean, options, email, phone, url, range, or currency. format
and max_chars are rejected on columns where they do not apply.
Response
A sync request returns 200 with the dataset and every row:
{
"data": {
"dataset": {
"id": "cmg1x2y3z0000",
"entity_type": "company",
"mode": "sync",
"status": "completed",
"columns": [{ "key": "headcount" }],
"row_count": 1,
"rows_completed": 1,
"deadline_ms": 20000,
"freshness": { "max_age_days": null },
"include_unverified": false,
"created_at": "2026-10-14T18:00:00.000Z",
"completed_at": "2026-10-14T18:00:04.210Z",
"rows_url": "/v1/datasets/cmg1x2y3z0000/rows"
},
"rows": [
{
"id": "cmg1x2y3z0001",
"index": 0,
"status": "completed",
"seed": { "domain": "linear.app" },
"cells": {
"headcount": {
"value": "51–200",
"confidence": 0.93,
"status": "found",
"evidence": [
{
"url": "https://www.linkedin.com/company/linear",
"quote": "51-200 employees",
"observed_at": "2026-10-12T09:30:00.000Z"
}
],
"source": "ploid"
}
}
}
]
},
"meta": { "request_id": "req_123", "credits_charged": 1, "acu_charged": 1, "max_credits": 1, "remaining_credits": 249, "remaining_acu": 249 }
}Rows come back in the order you sent them, and cells has one entry per requested column. meta.credits_charged is what this request cost, and max_credits
is what it would have cost if every cell had been found.
Cell statuses
| Status | Meaning | value | Billed |
|---|---|---|---|
found | Sources agree on one answer with enough confidence. | The answer | Yes |
unverified | Candidates disagreed or were too weak. confidence shows how close the best one came. | null, or the best candidate with include_unverified | No |
not_found | No source had the field for this row. | null | No |
timeout | The deadline passed or a source failed before the cell was filled, or the row has not been filled yet. | null | No |
Confidence and evidence
confidence is between 0 and 1 and reflects how strongly independent sources agree on the value, weighted by how reliable each source is for that field and
how recently it was observed. It is null when nothing was found.
evidence lists up to three public pages that support a found value, each with the url, a short quote (up to 300 characters) when one is available, and
observed_at, when Ploid last read it. Evidence is empty for cells that were not found. Every cell reports "source": "ploid".
Found email.work, email.personal, and phone cells also carry validation: valid, risky, or unknown deliverability.
Sync and async
Sync requests (up to 25 rows) answer within deadline_ms. Ploid reads the most promising sources first and keeps reading while time remains, so a longer
deadline can fill more cells. Anything not filled in time returns timeout and is free.
For larger tables, send "mode": "async". The response is 202 with the dataset and no rows:
curl https://api.ploid.com/v1/datasets \
-H "Authorization: Bearer $PLOID_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "entity": { "type": "company" }, "mode": "async", "rows": [{ "domain": "linear.app" }], "columns": [{ "key": "industry" }] }'Poll GET /v1/datasets/{id} until status is completed or failed, and page rows from rows_url. Rows are readable while the dataset runs; a row still
pending reports timeout cells until it is filled.
| Endpoint | Use |
|---|---|
POST /v1/datasets | Create a dataset and fill it. |
GET /v1/datasets/{id} | Status and progress: status, row_count, rows_completed, completed_at. |
GET /v1/datasets/{id}/rows | Rows in seed order. limit defaults to 100 (up to 500); pass next_cursor as cursor. |
Dataset status is queued, running, completed, or failed.
Freshness
Ploid keeps what it has read and reuses it across datasets when it is recent enough. By default each field has its own window: a job title goes stale faster
than a founding year. Set freshness.max_age_days to cap the age of reused evidence for every column; 0 re-reads every source live, which costs more time
against the deadline.
{ "freshness": { "max_age_days": 7 } }Examples
Creator socials and contact
curl https://api.ploid.com/v1/datasets \
-H "Authorization: Bearer $PLOID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity": { "type": "person" },
"rows": [{ "name": "Marques Brownlee", "socials": { "youtube": "@mkbhd" } }],
"columns": [
{ "key": "socials.x" },
{ "key": "socials.instagram" },
{ "key": "website" },
{ "key": "email.work" }
],
"deadline_ms": 30000
}'Profiles discovered for a creator are accepted only when they link back to a profile or site you supplied, or match it closely enough; otherwise the cell is
unverified rather than a guess.
To review those guesses yourself, set include_unverified: true. Each unverified cell then carries its best candidate, and confidence is scaled by how
likely the profile belongs to your row: a profile that only shares the name scores below 0.1, while one just short of a confirming link scores close to 0.5.
Custom entity
{
"entity": { "type": "custom" },
"rows": [{ "name": "Golden Gate Park", "socials": { "website": "https://goldengatepark.com" } }],
"columns": [{ "key": "custom.opening_hours", "description": "Daily opening hours of the park", "format": "text" }]
}Pricing
Datasets are pay on hit. Only found cells are billed:
| Cell | Price |
|---|---|
| Built-in standard column | 1 ACU per 10 found cells, rounded up per request |
custom.* column | 1 ACU per found cell |
email.work | 1 ACU per found cell |
email.personal | 3 ACU per found cell |
phone | 10 ACU per found cell |
not_found, unverified, timeout | Free |
A request needs a positive balance to start. The API key's daily and monthly budgets reserve the most the request could cost (every cell found) and release
the rest once it finishes. Sync requests are charged when they return; async datasets are charged once when they finish. If a sync request fails partway, cells
found before the failure are billed and readable through the dataset_id in the error.
Errors
| Status | Code | When |
|---|---|---|
402 | insufficient_acu | The workspace has no balance. |
402 | daily_budget_exceeded, monthly_budget_exceeded | The API key's budget cannot cover the request's maximum cost. |
404 | not_found | The dataset does not exist in your workspace. |
422 | validation_failed | The body is invalid. error.details.fieldErrors is keyed by path, such as rows.3.linkedin_url or columns.1.key. |
503 | upstream_unavailable | Filling is temporarily unavailable. Retry shortly. |
See Errors and limits for rate limits and retries.
