{"openapi":"3.1.0","info":{"title":"DDSFAX API","version":"1.0.0","description":"HIPAA-compliant fax API for dental practices. Send faxes, list and search transmissions, read AI document-intelligence results, and manage fax numbers. Authenticate every request with an API key (`Authorization: Bearer dds_...`) generated in the DDSFAX dashboard. API access requires an active subscription.","contact":{"url":"https://www.ddsfax.com/contact","email":"support@ddsfax.com"},"termsOfService":"https://www.ddsfax.com/legal/terms"},"servers":[{"url":"https://www.ddsfax.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from the DDSFAX dashboard. Keys are prefixed with `dds_`. Example: `Authorization: Bearer dds_abc123`."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message."},"code":{"type":"string","description":"Machine-actionable error code, e.g. unauthorized, subscription_required, invalid_number, invalid_media_url, provider_timeout, provider_unavailable."},"retryable":{"type":"boolean","description":"Whether retrying the same request (with the same idempotency_key) can succeed."},"suggestion":{"type":"string","description":"Suggested corrective action, written for an AI agent caller to self-correct."}},"required":["error"]},"Fax":{"type":"object","description":"A fax record.","properties":{"id":{"type":"string","description":"DDSFAX fax ID."},"telnyxFaxId":{"type":"string","description":"Carrier-side fax ID."},"direction":{"type":"string","enum":["inbound","outbound"]},"fromNumber":{"type":"string","description":"Sender, E.164."},"toNumber":{"type":"string","description":"Recipient, E.164."},"status":{"type":"string","description":"Lifecycle status, e.g. queued, sending, delivered, failed, received."},"pageCount":{"type":["integer","null"]},"createdAt":{"type":"string","description":"ISO-8601 timestamp."}}}}},"paths":{"/api/v1/faxes":{"post":{"operationId":"sendFax","summary":"Send a fax","description":"Sends a PDF to a fax number. Provide `to` (E.164 or 10-digit US) or `npi` (10-digit provider NPI — the fax number on record is auto-resolved via NPPES). The document must be a publicly fetchable HTTPS URL ending in `.pdf`. Returns 202 immediately with status `queued`; poll `GET /api/v1/faxes/{id}` for delivery status. Returns 402 if the account has no active subscription.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["media_url"],"properties":{"to":{"type":"string","description":"Destination fax number: 10-digit US number or E.164 (e.g. +13055551234). Required unless npi is provided."},"npi":{"type":"string","pattern":"^[0-9]{10}$","description":"Provider NPI; the fax on record is resolved via the NPPES NPI Registry. Returns 422 no_fax_on_record if the provider has no fax listed."},"media_url":{"type":"string","format":"uri","description":"HTTPS URL of the PDF to send. Must end in .pdf."},"from":{"type":"string","description":"Optional sending number you own. Defaults to your practice fax number."},"quality":{"type":"string","enum":["normal","high","very_high"],"default":"high","description":"Transmission quality."},"idempotency_key":{"type":"string","minLength":8,"maxLength":128,"description":"Strongly recommended for automated callers. Pass a fresh UUID per logical send; retrying with the same key within 24h replays the original result instead of transmitting the document twice."}}}}}},"responses":{"202":{"description":"Fax accepted and queued for transmission.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"DDSFAX fax ID — use for status polling."},"telnyx_fax_id":{"type":"string"},"status":{"type":"string","const":"queued"},"to":{"type":"string"},"from":{"type":"string"}}}}}},"400":{"description":"Invalid number, URL, or body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Active subscription required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Carrier temporarily unavailable — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listFaxes","summary":"List faxes","description":"Lists your faxes, newest first. Filter by direction and paginate with limit/offset.","parameters":[{"name":"direction","in":"query","schema":{"type":"string","enum":["inbound","outbound"]},"description":"Filter by direction."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Page of fax records.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Fax"}},"meta":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"count":{"type":"integer"}}}}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/faxes/{id}":{"get":{"operationId":"getFax","summary":"Get fax status and metadata","description":"Returns a single fax by DDSFAX ID or carrier fax ID, including live carrier delivery status in `telnyx_status`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Fax record with live status.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Fax"},{"type":"object","properties":{"telnyx_status":{"type":"string"}}}]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No fax with that ID on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/faxes/search":{"get":{"operationId":"searchFaxes","summary":"Search faxes by content, document type, or urgency","description":"Full-text search over OCR text plus filters on AI-classified document type and urgency. At least one of q, type, or urgency is required. Results include match snippets and extracted data.","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Full-text query over OCR-indexed fax content."},{"name":"type","in":"query","schema":{"type":"string"},"description":"AI document type, e.g. referral, eob, lab_order, prescription, records_request."},{"name":"urgency","in":"query","schema":{"type":"string","enum":["routine","urgent","stat"]}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100,"default":25}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Matching faxes with snippets, doc_type, urgency, confidence, and extracted_data."},"400":{"description":"No filter provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/faxes/{id}/intelligence":{"get":{"operationId":"getFaxIntelligence","summary":"Get AI document-intelligence results for a fax","description":"Returns the AI processing result for an inbound fax: document classification (doc_type, doc_subtype, confidence, urgency), OCR quality, structured extracted_data, and named entities. If processing has not completed, returns status `pending`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Intelligence result (or {status: \"pending\"} while processing).","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"pending | completed | failed"},"doc_type":{"type":["string","null"],"description":"e.g. referral, eob, lab_order, prescription, records_request"},"doc_subtype":{"type":["string","null"]},"confidence":{"type":["number","null"],"description":"0–100 classification confidence."},"urgency":{"type":["string","null"],"enum":["routine","urgent","stat",null]},"ocr_quality":{"type":["string","null"],"enum":["high","medium","low","failed",null]},"ocr_page_count":{"type":["integer","null"]},"extracted_data":{"type":["object","null"],"description":"Structured fields extracted from the document."},"entities":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"meta":{"type":["string","null"]}}}}}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/numbers":{"get":{"operationId":"searchAvailableNumbers","summary":"Search available fax numbers","description":"Searches carrier inventory for available numbers by state, city, or area code.","parameters":[{"name":"state","in":"query","schema":{"type":"string"},"description":"Two-letter US state code."},{"name":"city","in":"query","schema":{"type":"string"}},{"name":"area_code","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Available numbers with metadata."},"400":{"description":"Invalid search criteria.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Carrier rate limit — retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"provisionNumber","summary":"Provision a fax number","description":"Purchases an available number found via search and assigns it to your account. Requires an active subscription.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","description":"E.164 number from search results."}}}}}},"responses":{"201":{"description":"Number order placed; order status in `data.status`."},"400":{"description":"Missing phone_number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Active subscription required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/providers/resolve":{"get":{"operationId":"resolveProvider","summary":"Resolve a provider to fax candidates (NPPES-backed)","description":"Resolves a US healthcare provider to verified fax candidates using the public NPPES NPI Registry. Search by npi (exact) or name, narrowing with state and city. Candidates with a fax number on record sort first. Confirm the intended recipient before sending PHI to a resolved number.","parameters":[{"name":"npi","in":"query","schema":{"type":"string","pattern":"^[0-9]{10}$"},"description":"Exact 10-digit NPI lookup."},{"name":"name","in":"query","schema":{"type":"string"},"description":"Provider or organization name, e.g. \"Alice Chen\"."},{"name":"state","in":"query","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"city","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20,"default":5}}],"responses":{"200":{"description":"Candidate providers with npi, name, credential, taxonomy, address, and E.164 fax/phone where on record."},"400":{"description":"Missing or invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"NPI Registry temporarily unavailable — retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/review-queue":{"get":{"operationId":"listReviewQueue","summary":"List faxes held for human review","description":"Inbound faxes held for review — low AI-classification confidence, sensitive document classes, failed processing, or routing rules. Filter with ?status=pending|approved|corrected|dismissed (default pending). Each item includes the AI summary, confidence, and an SLA timestamp.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","approved","corrected","dismissed"],"default":"pending"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Review items with fax metadata and AI summaries."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/review-queue/{id}/resolve":{"post":{"operationId":"resolveReviewItem","summary":"Resolve a review item","description":"Resolves a pending review item: approve (AI was right), dismiss (not actionable), or correct (supply corrected_doc_type; the human label replaces the AI classification and the correction is kept as eval data).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["approve","dismiss","correct"]},"corrected_doc_type":{"type":"string","description":"Required when action is \"correct\"."},"corrected_data":{"type":"object","description":"Optional corrected extraction fields."}}}}}},"responses":{"200":{"description":"Item resolved."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No review item with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Item already resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/routing-rules":{"get":{"operationId":"listRoutingRules","summary":"List routing rules","description":"The practice's automation rules in evaluation order (ascending priority).","responses":{"200":{"description":"Rules with match conditions and actions."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createRoutingRule","summary":"Create a routing rule","description":"Creates a post-classification automation rule. Match conditions (doc_type, urgency, OCR keyword) AND together; null fields are wildcards. Actions: always_review, skip_review (raise the automation dial for a class), set_urgency (params {\"urgency\":\"stat\"}), notify_email (params {\"email\":\"...\"}).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","action"],"properties":{"name":{"type":"string"},"action":{"type":"string","enum":["always_review","skip_review","set_urgency","notify_email"]},"priority":{"type":"integer","default":100,"description":"Lower evaluates first."},"enabled":{"type":"boolean","default":true},"match_doc_type":{"type":["string","null"]},"match_urgency":{"type":["string","null"],"enum":["routine","urgent","stat",null]},"match_keyword":{"type":["string","null"],"description":"Case-insensitive substring of OCR text."},"action_params":{"type":["object","null"]}}}}}},"responses":{"201":{"description":"Rule created."},"400":{"description":"Invalid rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/routing-rules/{id}":{"patch":{"operationId":"updateRoutingRule","summary":"Update a routing rule","description":"Partial update — toggle enabled, change priority, or edit match conditions and params.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rule updated."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No rule with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteRoutingRule","summary":"Delete a routing rule","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rule deleted."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No rule with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/usage":{"get":{"operationId":"getAccountUsage","summary":"Get account usage for the current period","description":"Returns plan status and calendar-month usage: faxes and pages sent/received plus AI document-intelligence operations. DDSFAX plans include unlimited pages, so counts are informational rather than quota limits.","responses":{"200":{"description":"Plan and usage summary.","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"granularity":{"type":"string","const":"calendar_month"}}},"plan":{"type":"object","properties":{"status":{"type":["string","null"]},"pages_included":{"type":"string","const":"unlimited"},"overage_fees":{"type":"boolean"}}},"usage":{"type":"object","properties":{"faxes_sent":{"type":"integer"},"pages_sent":{"type":"integer"},"faxes_received":{"type":"integer"},"pages_received":{"type":"integer"},"ai_operations":{"type":"integer"}}}}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/numbers/{id}":{"get":{"operationId":"getNumber","summary":"Get an owned number","description":"Looks up an owned number by carrier ID or E.164 phone number.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Number details."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Number not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"releaseNumber","summary":"Release a number","description":"Releases an owned number back to the carrier. This is irreversible.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Number released."},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Release failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}