Introduction
The FieldSwap API is a single-shot identity-data API. You send one piece of identity data (email, phone, LinkedIn URL, IP, or full name), and ask for another field back (phone, employment, social, address, etc.). On a cache miss FieldSwap cascades through configured third-party data vendors and persists the result, so the same lookup is free next time within the field's TTL.
All product endpoints live under /v1/. Breaking changes ship under a new major version; backwards-compatible additions (new optional fields, new outputtypes) ship within /v1/.
Authentication
Every /v1/* request requires an API key. Generate one in the API keys tab. Keys are shown once on creation — store them in a secret manager, not in source.
You can authenticate two ways. They are equivalent; pick whichever your stack prefers:
Authorization: Bearer fs_live_<your_key> # or X-Api-Key: fs_live_<your_key>
Keys are scoped to a single workspace (tenant). Treat them like passwords: rotate periodically, revoke any key that may have leaked, and use a separate key per environment or service so you can revoke without coordinated downtime.
fs_live_ keys — proxy through your own backend instead.Lookup
Resolve a single output field for one identity input. Each call costs 1 credit regardless of whether the result was served from cache or freshly fetched from a vendor. If the cascade returns no data the call still costs 1 credit (this matches the underlying vendor billing model — you pay per query, not per match).
Request body
{
"input": { "type": "<field-type>", "value": "<string>" },
"output": "<field-type>"
}| Field | Type | Description |
|---|---|---|
| input.type | FieldType | Kind of identity supplied. See Field types. |
| input.value | string (1–500) | Raw value. Emails are lowercased, phones parsed to E.164, LinkedIn normalized to canonical URL. |
| output | FieldType | Field you want returned. Can equal input.type (useful for canonicalization). |
Example
curl -X POST https://api.fieldswap.founderscale.com/v1/lookup \
-H "Authorization: Bearer fs_live_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "email", "value": "ada@example.com" },
"output": "phone"
}'Response
{
"request_id": "8f1d2c20-…",
"person_id": "0a4e5b66-…",
"input": { "type": "email", "value": "ada@example.com" },
"output": {
"type": "phone",
"value": { "phone_e164": "+14155551234" },
"fetched_at": "2026-05-08T17:40:21.118Z",
"source_vendor": "exampledata",
"fresh": true
},
"hit_source": "vendor",
"credits_charged": 1,
"credits_remaining": 9847,
"status": "ok"
}| Field | Description |
|---|---|
| request_id | UUID. Echoed in x-request-id response header. Quote when filing support. |
| person_id | Stable identifier for the resolved identity in your workspace. null if no person was matched or created. |
| output | The resolved field, or null if status is not_found / opt_out. |
| output.value | Field-specific shape. See Field types. |
| output.fresh | true if served from cache within TTL or freshly fetched. |
| hit_source | cache, vendor, or miss. Use to gauge cache effectiveness. |
| status | ok, not_found, or opt_out. 200 OK is returned for all three — check this field. |
not_found means we know who the person is but no vendor returned the requested field. opt_out means the person has invoked their right to deletion or opted out of enrichment — your code should treat both identically (no value, no retry needed for some hours).Field types
Both input.type and output use the same enum. Some fields are only meaningful as inputs (ip) or as outputs (employment, social) — supplying them in the wrong direction returns 400.
| Type | Direction | Value shape |
|---|---|---|
| in / out | { "email": "ada@example.com" } | |
| phone | in / out | { "phone_e164": "+14155551234" } |
| in / out | { "url": "https://linkedin.com/in/ada" } | |
| first_name | out | { "value": "Ada" } |
| last_name | out | { "value": "Lovelace" } |
| full_name | in / out | { "value": "Ada Lovelace" } |
| birthdate | out | { "value": "1815-12-10" } |
| address | out | { "line1": "...", "city": "...", "region": "CA", "postal_code": "94110", "country": "US" } |
| company | out | { "name": "Analytical Engines Ltd", "domain": "ae.example" } |
| title | out | { "value": "Mathematician" } |
| employment | out | { "company": "...", "title": "...", "start_date": "2024-01-01" } |
| social | out | { "twitter": "@ada", "github": "ada", "linkedin": "..." } |
| ip | in only | { "ip": "203.0.113.42" } → resolves to company / employment |
Normalization. Inputs are always normalized before lookup, so Ada@Example.com and ada@example.com hit the same cache row. Phones are parsed to E.164; if parsing fails we return 400 with {"detail":"could not parse phone"}.
Errors
Errors are returned with an HTTP status code and a JSON body of the shape { "detail": "<message>" }. The x-request-id header is set on every response — quote it when contacting support.
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Input could not be parsed (e.g. invalid email or phone number). |
| 401 | unauthorized | Missing, malformed, or revoked API key. |
| 402 | payment_required | Insufficient credits, or workspace daily provider cap reached. |
| 403 | forbidden | Authenticated, but not allowed (e.g. dashboard-only endpoint hit with an API key). |
| 404 | not_found | Resource does not exist or is not visible to your tenant. |
| 429 | rate_limited | Per-key request rate limit exceeded. Retry-After header included. |
| 5xx | server_error | Transient failure. Safe to retry with exponential backoff. |
Retry guidance. Lookups are not idempotent (each call costs a credit), but retries on 429 and 5xx are safe — a re-issued lookup within the cache TTL hits the cache and skips vendor work. Always honor Retry-After on 429.
Rate limits
Limits are enforced per API key. The default is 100 requests / minute per key; contact us to raise it. Every response includes:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1715192461 # unix seconds Retry-After: 23 # only on 429
On top of per-key throttling there is a per-workspace daily provider spend cap — once your configured cap is hit, lookups return 402 payment_required until the next UTC day. Configure the cap in Billing.
Logs & usage
Every request is logged with input/output type, hit source, latency, status, and the person it resolved to. View them in the Logs tab, or pull them programmatically:
GET /logs?limit=100&cursor=2026-05-08T00:00:00Z GET /billing/usage # current period credits + request count GET /billing/ledger # credit ledger entries
These dashboard endpoints require a JWT session, not an API key — they exist for the FieldSwap UI. If you want programmatic access to usage data, request a reporting key (separate scope) via support.
Privacy endpoints
Two unauthenticated endpoints exist so end users (data subjects) can invoke their privacy rights without going through your support team. You do not call these from your backend; you link to them from your privacy policy.
Submit a DSAR
{
"requester_email": "ada@example.com",
"type": "access" | "delete",
"identity_type": "email",
"identity_value": "ada@example.com",
"note": "optional free text"
}Opt out
{
"identity_type": "email",
"identity_value": "ada@example.com",
"reason": "optional"
}Opt-out is permanent for the identity until reversed by support. Subsequent lookups for an opted-out person return status: opt_out with no output and zero credits charged.
No-code (Zapier & Make)
You don't need to write code to call FieldSwap. Revenue-ops teams can wire /v1/lookup into a Zapier Zap or a Make scenario using each tool's generic HTTP step — no official app required. A typical flow: a new lead lands in your CRM, FieldSwap resolves their email into a phone number, and the result is written back to the record.
Step-by-step guides for each tool:
These cover the enrichment (action) direction only. A FieldSwap-as-a-trigger integration isn't available yet.
SDKs & samples
The API is small enough that an SDK isn't required — any HTTP client works. Official client libraries for Node and Python are on the roadmap.
For interactive testing without writing code, use the Playground. It runs against your live workspace and deducts real credits.
Found a bug or have a question? Email support@fieldswap.io with the failing request_id from x-request-id.