PloidDocs
Fill columns

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/datasets

A 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" }
    ]
  }'
FieldMeaning
entity.typeperson, company, or custom. Decides which built-in columns are allowed.
rows1 to 25 rows for sync requests, up to 2,000 with mode: "async".
columns1 to 30 columns with unique keys.
modesync (default) answers in the response. async returns 202 and fills rows in the background.
deadline_msSync only. 1,000 to 60,000; default 20,000. Cells still being read at the deadline return timeout. Ignored for async.
freshness.max_age_days0 to 3,650. The oldest cached evidence reused without a live read; 0 re-reads everything. Omitted, each field uses its own window.
include_unverifiedDefault 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.

FieldMeaning
namePerson, company, or entity name.
domainThe company's or person's own domain, such as linear.app.
linkedin_urlA LinkedIn person (linkedin.com/in/…) or company (linkedin.com/company/…) URL.
socialsHandles or profile URLs keyed by linkedin, x, instagram, youtube, tiktok, github, or website.
emailA known email address.
companyA person's employer, used to disambiguate common names.
extraFree-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.

EntityBuilt-in columns
companyname, domain, description, founded_year, headquarters, headcount, industry, linkedin_url, page_text, socials.*
personname, title, company, location, email.work, email.personal, phone, website, socials.*
customname, 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

StatusMeaningvalueBilled
foundSources agree on one answer with enough confidence.The answerYes
unverifiedCandidates disagreed or were too weak. confidence shows how close the best one came.null, or the best candidate with include_unverifiedNo
not_foundNo source had the field for this row.nullNo
timeoutThe deadline passed or a source failed before the cell was filled, or the row has not been filled yet.nullNo

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.

EndpointUse
POST /v1/datasetsCreate a dataset and fill it.
GET /v1/datasets/{id}Status and progress: status, row_count, rows_completed, completed_at.
GET /v1/datasets/{id}/rowsRows 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:

CellPrice
Built-in standard column1 ACU per 10 found cells, rounded up per request
custom.* column1 ACU per found cell
email.work1 ACU per found cell
email.personal3 ACU per found cell
phone10 ACU per found cell
not_found, unverified, timeoutFree

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

StatusCodeWhen
402insufficient_acuThe workspace has no balance.
402daily_budget_exceeded, monthly_budget_exceededThe API key's budget cannot cover the request's maximum cost.
404not_foundThe dataset does not exist in your workspace.
422validation_failedThe body is invalid. error.details.fieldErrors is keyed by path, such as rows.3.linkedin_url or columns.1.key.
503upstream_unavailableFilling is temporarily unavailable. Retry shortly.

See Errors and limits for rate limits and retries.