API reference · v1
Developers
Base URL: https://<your-app-domain>/api/public. All requests use JSON and Authorization: Bearer <key>. Create keys in the app under API Keys.
Keys and modes
ds_test_…keys return labeled synthetic results ("mode": "demo") and never spend credits.ds_live_…keys run real screenings and spend one credit per completed scan. If live screening isn't configured, you get a503 provider_not_configured— never a mock result.- Secrets are shown once at creation and stored only as hashes. Revoked keys fail immediately.
Endpoints
| POST | /v1/scan | Screen one phone number. Requires Idempotency-Key. |
| POST | /v1/batches | Create a batch from up to 10,000 numbers. Requires Idempotency-Key. |
| GET | /v1/batches/{id} | Batch status and counts. Each poll advances processing. |
| GET | /v1/batches/{id}/results | One row per submitted number. Paginate with offset & limit (max 1000). |
| GET | /v1/balance | Available and reserved credits. |
Scan a number
curl -X POST "https://<your-app-domain>/api/public/v1/scan" \
-H "Authorization: Bearer $TCPA_FIREWALL_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"phone": "+12105550100"}'Response
{
"request_id": "req_demo_001",
"mode": "demo",
"status": "completed",
"phone": "+13055550142",
"screening": {
"status": "match",
"message": "A screening list match was detected. Review before outreach.",
"reason_codes": [
"tf_litigation_association_b"
],
"checked_lists": null,
"match_count": 1,
"signals": [
{
"code": "tf_litigation_association_b",
"label": "Litigation-party list association · B"
}
]
},
"carrier": {
"name": "Example Wireless PCS",
"line_type": "mobile",
"raw_type": "PCS",
"source": "reported"
},
"number_region": {
"country": "US",
"state": "FL",
"city": null,
"rate_center": null,
"timezone": null,
"source": "carrier_metadata",
"is_estimate": true
},
"network": {
"lrn": "3055550142",
"ocn": "6529",
"lata": "460",
"clli": "MIAQFLAYDS0",
"port_type": "wireless"
},
"checked_at": "2026-10-29T12:00:00Z",
"source_updated_at": null,
"credits_used": 0,
"error": null
}Response fields
statuscompleted · failed · unknown. Only completed scans are billed.
screening.statusno_match (“No matches found in checked lists.”) · match · unknown. Failed or malformed provider responses are always unknown, never no_match.
screening.signalsTCPA Firewall signal objects with code and label. Categories are listed below; unspecified positive matches use tf_list_association. A signal does not establish a legal violation or verified identity.
screening.match_countReported match count, or null when unavailable. Not a count of lawsuits or plaintiffs.
screening.reason_codesOriginal TCPA Firewall codes matching screening.signals. Upstream feed identifiers are not exposed. A match does not establish verified identity.
screening.checked_listsnull unless list coverage is explicitly configured for your account. Federal/state DNC is never implied.
carrier.name / line_type / raw_typeThe reported carrier. line_type is mobile · landline · voip · toll_free · unknown. Missing data stays null/unknown.
number_regionState and rate center from carrier metadata. city and timezone stay null unless supported by data. is_estimate is always true.
networkReported lrn, ocn, lata, clli and port_type. port_type is an uninterpreted source value, not confirmation of porting history.
checked_atWhen our lookup ran. source_updated_at is null — the provider doesn't expose data age.
credits_used0 or 1. Demo mode always 0.
TCPA Firewall screening outcomes
| Outcome | Meaning |
|---|---|
| No list match detected | Successful screening with no reported matches. Not permission to call. |
| Risk signal detected | One or more list matches reported. Review before outreach. |
| Screening inconclusive | Failed, contradictory or unrecognized response. No credit charged. |
Signal categories
tf_complaint_associationComplaint record associationtf_pre_dispute_aPre-dispute list association · Atf_pre_dispute_bPre-dispute list association · Btf_legal_association_bLegal-professional list association · Btf_litigation_association_bLitigation-party list association · Btf_legal_association_aLegal-professional list association · Atf_litigation_association_aLitigation-party list association · A
A and B distinguish list categories, not risk levels, lawsuit probability or a verified relationship to the current subscriber. Unrecognized responses remain inconclusive; unfamiliar positive-match categories receive tf_list_association.
Supplied response · anonymized
{
"phone": "+1 (321) ***-****",
"screening": {
"status": "match",
"message": "A screening list match was detected. Review before outreach.",
"reason_codes": [
"tf_litigation_association_b"
],
"checked_lists": null,
"match_count": 1,
"signals": [
{
"code": "tf_litigation_association_b",
"label": "Litigation-party list association · B"
}
]
},
"carrier": {
"name": "T-Mobile",
"line_type": "mobile",
"raw_type": "PCS",
"source": "reported"
},
"number_region": {
"country": "US",
"state": "FL",
"city": null,
"rate_center": null,
"timezone": null,
"source": "carrier_metadata",
"is_estimate": true
}
}Carrier, line type and Florida region come from the supplied response. No contact name, city, timezone or lawsuit count is inferred. A match count is not a count of plaintiffs or lawsuits.
Only configured screening lists are checked. Federal DNC, reassigned-number coverage and other separate screening services are not implied by these results.
Batches
Numbers are normalized before deduplication. Credits for valid unique numbers are reserved up front; unused credits are released when the batch completes or is cancelled. Invalid rows and duplicates are never billed — duplicates reference the shared result row.
# 1. Create
curl -X POST "https://<your-app-domain>/api/public/v1/batches" -H "Authorization: Bearer $TCPA_FIREWALL_KEY" \
-H "Idempotency-Key: list-2026-10-29" -H "Content-Type: application/json" \
-d '{"phones": ["+12105550100", "210-555-0100", "not a number"]}'
# 2. Poll until status is "completed"
curl "https://<your-app-domain>/api/public/v1/batches/<id>" -H "Authorization: Bearer $TCPA_FIREWALL_KEY"
# 3. Fetch results
curl "https://<your-app-domain>/api/public/v1/batches/<id>/results?offset=0&limit=100" -H "Authorization: Bearer $TCPA_FIREWALL_KEY"Idempotency
Idempotency-Key is required on POST /v1/scan and POST /v1/batches. It is scoped to your workspace and operation. Repeating a request returns the original response (header Idempotent-Replayed: true) without charging again. Reusing a key with a different body returns 409.
Errors
{
"error": {
"type": "billing_error",
"code": "insufficient_credits",
"message": "Not enough credits. Purchase credits to run live scans.",
"request_id": "req_…"
}
}| 400 | invalid_request_error | invalid_json · invalid_body · idempotency_key_required |
| 401 | authentication_error | invalid_api_key (missing, malformed or revoked) |
| 402 | billing_error | insufficient_credits |
| 403 | permission_error | insufficient_permissions · workspace_suspended |
| 404 | invalid_request_error | not_found |
| 409 | idempotency_error | idempotency_key_reused · request_in_progress |
| 422 | invalid_request_error | invalid_phone |
| 429 | rate_limit_error | rate_limited (see Retry-After) |
| 503 | service_unavailable | provider_not_configured |
Rate limits
Each key has a per-minute limit (default 60, configurable per key). Exceeding it returns 429 with a Retry-After header in seconds. We don't publish throughput or latency figures that haven't been measured.