{"openapi":"3.0.3","info":{"title":"Phone List Cleaner API","version":"1.0.0","description":"Public REST API for cleaning phone numbers. Authenticate with a Bearer API key created at /app/api-keys.","contact":{"name":"Phone List Cleaner","email":"hello@phonelistcleaner.com"}},"servers":[{"url":"/","description":"Same host as the dashboard (Wasp serves /v1/* alongside the React app)."}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"plc_<32 base32 chars>","description":"Get a key at https://phonelistcleaner.com/app/api-keys. Keys are shown once at creation; we store only the hash. Revoked keys and suspended accounts return 401."}},"schemas":{"LineType":{"type":"string","enum":["mobile","landline","voip","toll_free","unknown"]},"Reason":{"type":"string","enum":["valid_mobile","valid_landline","valid_voip","valid_toll_free","invalid_number","duplicate","unknown_line_type","provider_unavailable","malformed_input"]},"SingleLookupResult":{"type":"object","required":["input_phone","normalized_e164","valid","country","country_name","region","line_type","carrier","sms_ready","call_ready","reason","cached","checked_at","error_code","credits_debited"],"properties":{"input_phone":{"type":"string","example":"+447400123456"},"normalized_e164":{"type":["string","null"],"example":"+447400123456"},"valid":{"type":["boolean","null"],"example":true},"country":{"type":["string","null"],"example":"GB"},"country_name":{"type":["string","null"],"example":"United Kingdom"},"region":{"type":["string","null"],"example":"Europe"},"line_type":{"$ref":"#/components/schemas/LineType"},"carrier":{"type":["string","null"],"example":"EE"},"sms_ready":{"type":"boolean","example":true},"call_ready":{"type":"boolean","example":true},"reason":{"$ref":"#/components/schemas/Reason"},"cached":{"type":"boolean","example":false},"checked_at":{"type":"string","format":"date-time"},"error_code":{"type":["string","null"],"example":null},"credits_debited":{"type":"integer","minimum":0,"example":1}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"missing_or_invalid_api_key"},"detail":{"description":"Optional additional context.","nullable":true}}},"BulkResultRow":{"type":"object","required":["index"],"properties":{"index":{"type":"integer","example":0},"result":{"$ref":"#/components/schemas/SingleLookupResult"},"error":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"paths":{"/v1/lookup":{"post":{"summary":"Look up a single phone number.","operationId":"v1Lookup","tags":["Lookup"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","example":"+447400123456"},"defaultCountry":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 code. Used when `phone` is in national format.","example":"GB"}}}}}},"responses":{"200":{"description":"Cleaned result. Soft failures (`malformed_input`, `provider_unavailable`) also return 200; check `reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SingleLookupResult"}}},"headers":{"X-RateLimit-Limit-Minute":{"schema":{"type":"integer"}},"X-RateLimit-Remaining-Minute":{"schema":{"type":"integer"}},"X-RateLimit-Limit-Day":{"schema":{"type":"integer"}},"X-RateLimit-Remaining-Day":{"schema":{"type":"integer"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Out of credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Idempotency-Key collision (key reused with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","maxLength":255},"required":false,"description":"Optional caller-chosen key. Same key + same body within 24h returns the cached response (no extra debit). Same key + different body → 409."}]}},"/v1/account":{"get":{"summary":"Read-only account snapshot, balance, key meta, rate-limit state.","operationId":"v1Account","tags":["Account"],"responses":{"200":{"description":"Account snapshot.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/batches":{"get":{"summary":"List your 25 most recent batches.","operationId":"v1ListBatches","tags":["Batches"],"responses":{"200":{"description":"Batches array (newest first).","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing \"bulk\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/batches/{id}":{"get":{"summary":"Read a single batch's status + counters.","operationId":"v1GetBatch","tags":["Batches"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Batch summary.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing \"bulk\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found or not owned by caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/batches/{id}/results":{"get":{"summary":"Get signed download URLs for both result CSVs.","operationId":"v1GetBatchResults","tags":["Batches"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Pre-signed URLs valid for ~10 minutes.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing \"bulk\" scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found or not owned by caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Batch not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/lookup/bulk":{"post":{"summary":"Look up 1-100 phone numbers in one request.","operationId":"v1LookupBulk","tags":["Lookup"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phones"],"properties":{"phones":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","example":"+447400123456"}},"defaultCountry":{"type":"string","minLength":2,"maxLength":2,"example":"GB"}}}}}},"responses":{"200":{"description":"Result array in input order. `creditsExhausted` flips true if credits run out mid-batch.","content":{"application/json":{"schema":{"type":"object","required":["count","creditsExhausted","results"],"properties":{"count":{"type":"integer"},"creditsExhausted":{"type":"boolean"},"results":{"type":"array","items":{"$ref":"#/components/schemas/BulkResultRow"}}}}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing / invalid / revoked / suspended key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Idempotency-Key collision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","maxLength":255},"required":false,"description":"Optional caller-chosen key. Same key + same body within 24h returns the cached response."}]}}}}