SDKs and tools
Official clients for Python, TypeScript and Go, and an MCP server for agents.
On this page
The API is plain HTTP and JSON and you can call it with anything. These exist because four decisions are easy to get subtly wrong by hand, and getting one of them wrong costs money rather than raising an error.
| Language | Install | Dependencies |
|---|---|---|
| Python 3.10+ | pip install easydata-api | None |
| TypeScript / Node 18+ | npm install @easydata.win/sdk | None |
| Go 1.23+ | go get github.com/easydata-win/easydata-go | None |
| Agents (MCP) | npx -y @easydata.win/mcp | - |
What they do that a hand-rolled client usually does not #
- A submission carries an Idempotency-Key the client mints. Without one, a retry of a submission that actually succeeded creates a second batch and bills for it. This is the most valuable line in any of them.
- Retries cover only what is safe to repeat - 429, 5xx and a transport failure. A 4xx is raised immediately, because repeating a refusal only spends your rate budget on a certain no.
- Backoff honours Retry-After when we send one, and is jittered otherwise. The failure that produces a retry storm is the one every client sees at the same instant.
- The results cursor is an iterator that keeps up with the batch: it yields rows while the batch is still processing and stops when the batch reaches a terminal state. Each one also has a stream() that holds one SSE connection open instead of polling, and reconnects from the exact cursor if it drops.
- An absent rate-limit header reads as "no ceiling", not as zero. Reading a missing header as 0 makes an account with no limit look completely blocked.
- Webhook verification, both schemes, including the checks that are silent when you skip them.
Python #
from easydata_api import EasyData
ed = EasyData() # reads EASYDATA_API_KEY
# One record, in this call, at twice the credits.
r = ed.profiles_enrich.sync("https://linkedin.com/in/satyanadella")
print(r.result.data["full_name"])
# A batch of any size, streamed as it drains.
batch = ed.profiles_enrich(urls, external_id="crm-sync")
for entry in ed.results(batch.batch_id):
if entry.ok:
save(entry.data)
else:
log(entry.input, entry.error["type"]) # and credits_used is 0TypeScript #
import { EasyData } from '@easydata.win/sdk';
const ed = new EasyData(); // reads EASYDATA_API_KEY
const r = await ed.profiles.enrich.sync('https://linkedin.com/in/satyanadella');
const batch = await ed.profiles.enrich(urls, { externalId: 'crm-sync' });
for await (const entry of ed.results(batch.batch_id)) {
if (entry.status === 'succeeded') save(entry.data);
}Go #
ed, err := easydata.New("") // reads EASYDATA_API_KEY
out, err := ed.Sync(ctx, easydata.OpProfilesEnrich,
"https://linkedin.com/in/satyanadella", nil)
b, err := ed.Submit(ctx, easydata.OpProfilesEnrich, targets, &easydata.SubmitOptions{
ExternalID: "crm-sync",
})
for entry, err := range ed.Results(ctx, b.BatchID, nil) {
if err != nil {
return err
}
save(entry)
}For agents: the MCP server #
An MCP server that hands an agent the operations as tools, with no integration code at all. Every tool description states its own price, because an agent choosing between a lookup and a search cannot weigh them otherwise.
{
"mcpServers": {
"easydata": {
"command": "npx",
"args": ["-y", "@easydata.win/mcp"],
"env": {
"EASYDATA_API_KEY": "pk_live_...",
"EASYDATA_MAX_CREDITS": "500"
}
}
}
}EASYDATA_MAX_CREDITS bounds what one session may spend, and it is checked before the call rather than after - so a refusal costs nothing. An agent in a loop is the failure mode a cap exists for, and a monthly allowance is far too coarse to catch it: by the time that trips, the month is gone. Every tool result carries the running total beside the record.
- profile_lookup, company_lookup, post_lookup, profile_activity - one entity, answered in the call.
- search_people, search_companies, search_employees - a pasted Sales Navigator search URL, one page of rows; search_employees runs a people search across a list of companies.
- submit_batch and batch_results - 1 to 50,000 targets, read page by page while it runs. Half the price of the lookups, and enrich: true on a search returns whole records instead of stubs.
- batch_status and account_usage - free.
Bulk is submit-then-read rather than one blocking tool, deliberately: a tool call that waited for a 50,000-row batch would time out, and the first records are readable long before the last one lands.
No SDK for your language #
Generate one from the OpenAPI 3.1 specification at /openapi.yaml, which describes every operation, all 68 schemas and all six webhooks. Read the errors and rate-limits guides first: a generated client will not know that a 4xx is not worth retrying, or that a submission wants an Idempotency-Key.