{"openapi":"3.1.0","info":{"title":"DNC LATAM API","version":"1.0.0","description":"Scrub phone numbers against Mexico's REPEP and Argentina's Registro Nacional No Llame. Customer phone numbers are processed with zero data retention."},"servers":[{"url":"https://api.dnclatam.com"}],"paths":{"/v1/health":{"get":{"summary":"Health check","operationId":"health","responses":{"200":{"description":"API is available","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}}}}},"/v1/ready":{"get":{"summary":"Readiness check","operationId":"readiness","description":"Returns 200 only while the process is accepting work and can reach Postgres.","responses":{"200":{"description":"API is ready to accept traffic","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"503":{"description":"API is draining or Postgres is unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false}},"required":["ok"]}}}}}}},"/v1/scrub":{"post":{"summary":"Scrub a batch of phone numbers","operationId":"scrubPhones","security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Opaque key for one logical request, scoped to the authenticated API key. Use a random UUID and never include phone numbers or other PII. Exact retries are protected for at least 7 days; only a keyed, non-reversible fingerprint is retained.","schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7e]+$"},"example":"018f47c2-9b0d-7c95-bf47-cd84c12f8912"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string","enum":["mx","ar"],"description":"Registry country."},"phones":{"type":"array","minItems":1,"maxItems":500000,"items":{"type":"string","maxLength":64},"description":"Phone numbers in international or supported local formats."}},"required":["country","phones"],"additionalProperties":false},"examples":{"mexico":{"value":{"country":"mx","phones":["+52 55 1234 5678","55 8765 4321"]}}}}}},"responses":{"200":{"description":"Scrub completed","headers":{"x-retention":{"schema":{"type":"string","const":"zero"},"description":"Customer phone numbers are not persisted."},"Idempotency-Replayed":{"schema":{"type":"string","const":"true"},"description":"Present when a completed request was recalculated without charging or recording usage again."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScrubResult"}}}},"400":{"description":"Missing or invalid Idempotency-Key, country, body, or batch size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The requested country is backed by Stripe and the subscription is inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionInactiveError"}}}},"403":{"description":"The organization is suspended or has no entitlement for this country","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AccountSuspendedError"},{"$ref":"#/components/schemas/CountryNotInPlanError"}]}}}},"409":{"description":"The same Idempotency-Key is still processing, or was reused with a different payload","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds before retrying an in-progress request. Present only for `idempotency_request_in_progress`."}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/IdempotencyInProgressError"},{"$ref":"#/components/schemas/IdempotencyConflictError"}]}}}},"413":{"description":"Request body exceeds 25 MiB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Temporary source-IP block, technical per-organization request rate limit, or monthly country quota exceeded. Inspect `error` to distinguish them.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds until another request can be attempted. Present for `temporarily_blocked` and `rate_limit_exceeded`."},"X-RateLimit-Limit":{"schema":{"type":"integer","const":60}},"X-RateLimit-Remaining":{"schema":{"type":"integer","minimum":0,"maximum":60}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix timestamp when the technical window resets."}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/TemporaryBlockError"},{"$ref":"#/components/schemas/RateLimitError"},{"$ref":"#/components/schemas/QuotaError"}]}}}},"503":{"description":"This API process is at its bounded database-preflight, in-flight request, or bulk-batch capacity; retry shortly","headers":{"Retry-After":{"schema":{"type":"integer","const":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapacityError"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"dnc_live_*"}},"schemas":{"InvalidPhone":{"type":"object","properties":{"input":{"type":"string"},"reason":{"type":"string","enum":["empty","unparseable","wrong_country","too_short","too_long"]}},"required":["input","reason"]},"ScrubResult":{"type":"object","properties":{"matched":{"type":"array","items":{"type":"string"},"description":"Original inputs found in the DNC registry."},"clean":{"type":"array","items":{"type":"string"},"description":"Original inputs not found in the DNC registry."},"invalid":{"type":"array","items":{"$ref":"#/components/schemas/InvalidPhone"}},"stats":{"type":"object","properties":{"checked":{"type":"integer"},"matched":{"type":"integer"},"invalid":{"type":"integer"}},"required":["checked","matched","invalid"]}},"required":["matched","clean","invalid","stats"]},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"]},"CapacityError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"capacity_exceeded"},"retry_after":{"type":"integer","const":1}},"required":["error","message","retry_after"]}]},"TemporaryBlockError":{"description":"The source IP triggered repeated unknown-route abuse protection.","allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"temporarily_blocked"},"retry_after":{"type":"integer","minimum":1,"maximum":900}},"required":["error","message","retry_after"]}]},"QuotaError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"monthly_quota_exceeded"},"country":{"type":"string","enum":["mx","ar"]},"limit":{"type":"integer","minimum":0},"used":{"type":"integer"},"requested":{"type":"integer"},"upgrade_url":{"type":"string","format":"uri"}},"required":["error","country","limit","used","requested","upgrade_url"]}]},"AccountSuspendedError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"account_suspended"}},"required":["error","message"]}]},"CountryNotInPlanError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"country_not_in_plan"}},"required":["error","message"]}]},"SubscriptionInactiveError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"subscription_inactive"}},"required":["error","message"]}]},"IdempotencyInProgressError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"idempotency_request_in_progress"}},"required":["error","message"]}]},"IdempotencyConflictError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"idempotency_key_reused"}},"required":["error","message"]}]},"RateLimitError":{"description":"The organization exceeded the shared technical request limit across all of its API keys.","allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","const":"rate_limit_exceeded"},"limit":{"type":"integer","const":60},"window_seconds":{"type":"integer","const":60},"retry_after":{"type":"integer","minimum":1}},"required":["error","limit","window_seconds","retry_after"]}]}}}}