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 a 503 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/scanScreen one phone number. Requires Idempotency-Key.
POST/v1/batchesCreate 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}/resultsOne row per submitted number. Paginate with offset & limit (max 1000).
GET/v1/balanceAvailable and reserved credits.

Scan a number

curl
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

200 OK · Demo — sample data (synthetic, not a real number)
{
  "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

status

completed · failed · unknown. Only completed scans are billed.

screening.status

no_match (“No matches found in checked lists.”) · match · unknown. Failed or malformed provider responses are always unknown, never no_match.

screening.signals

TCPA 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_count

Reported match count, or null when unavailable. Not a count of lawsuits or plaintiffs.

screening.reason_codes

Original TCPA Firewall codes matching screening.signals. Upstream feed identifiers are not exposed. A match does not establish verified identity.

screening.checked_lists

null unless list coverage is explicitly configured for your account. Federal/state DNC is never implied.

carrier.name / line_type / raw_type

The reported carrier. line_type is mobile · landline · voip · toll_free · unknown. Missing data stays null/unknown.

number_region

State and rate center from carrier metadata. city and timezone stay null unless supported by data. is_estimate is always true.

network

Reported lrn, ocn, lata, clli and port_type. port_type is an uninterpreted source value, not confirmation of porting history.

checked_at

When our lookup ran. source_updated_at is null — the provider doesn't expose data age.

credits_used

0 or 1. Demo mode always 0.

TCPA Firewall screening outcomes

OutcomeMeaning
No list match detectedSuccessful screening with no reported matches. Not permission to call.
Risk signal detectedOne or more list matches reported. Review before outreach.
Screening inconclusiveFailed, contradictory or unrecognized response. No credit charged.

Signal categories

  • tf_complaint_associationComplaint record association
  • tf_pre_dispute_aPre-dispute list association · A
  • tf_pre_dispute_bPre-dispute list association · B
  • tf_legal_association_bLegal-professional list association · B
  • tf_litigation_association_bLitigation-party list association · B
  • tf_legal_association_aLegal-professional list association · A
  • tf_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

TCPA Firewall interpretation · supplied result, not a new lookup
{
  "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.

batch.sh
# 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

402 Payment Required
{
  "error": {
    "type": "billing_error",
    "code": "insufficient_credits",
    "message": "Not enough credits. Purchase credits to run live scans.",
    "request_id": "req_…"
  }
}
400invalid_request_errorinvalid_json · invalid_body · idempotency_key_required
401authentication_errorinvalid_api_key (missing, malformed or revoked)
402billing_errorinsufficient_credits
403permission_errorinsufficient_permissions · workspace_suspended
404invalid_request_errornot_found
409idempotency_erroridempotency_key_reused · request_in_progress
422invalid_request_errorinvalid_phone
429rate_limit_errorrate_limited (see Retry-After)
503service_unavailableprovider_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.