{"openapi":"3.1.0","info":{"title":"Med Spa Radar API","version":"1.0.0","summary":"Aesthetic-practice regulatory monitoring: federal substance standing, per-state coverage posture, and pipeline freshness.","description":"## What this is\n\nA statute-cited regulatory reference for aesthetic practices, served as JSON. Every response\ncarries the freshness of the data behind it, the provenance of each field, and the licence\nterms it was served under.\n\n## What absence means\n\nAn empty result is never a bare empty array. Every response carries a `record` block that\ndistinguishes three different kinds of nothing:\n\n- `on_record` — we cover this scope and have data.\n- `not_on_record` — we cover this scope, we looked, there is genuinely nothing. Absence here is evidence.\n- `not_covered` — **we have not written this scope. Our silence says nothing about the underlying law.**\n\nA fourth, `undetermined`, appears only on error responses: the request was not fulfilled and\nno determination was made. It is never a finding.\n\nDo not treat `not_covered` as a clean bill of health. It is a statement about our coverage.\n\n## Freshness and degradation\n\nEvery response carries `freshness.pipeline`, scoped to the sources that feed that endpoint.\nWhen a feeding source is behind, the response still returns data with\n`freshness.pipeline.status = \"degraded\"` and a `Warning` header — an outage would be a worse\nanswer than a flagged one. Where pipeline health cannot be read at all the status is\n`\"unknown\"`, which is never an all-clear.\n\n`freshness.publication_latency` is measured end-to-end: from the date a source assigned an\nitem to the date it went live here. It is **not** a pipeline latency — it includes the time\nthe upstream source itself took to publish.\n\n## Attribution is a condition of use\n\nItems are marked `sourced` or `msr_analysis`. Where an item is derived from a source whose\nlicence requires attribution, the response `attribution` block names the source, the licence,\nand the exact statement that must travel with the data if you pass it on. MSR-original\nanalysis carries no such obligation and must not be attributed to an upstream body.\n\n## Versioning\n\n`/v1` pins the envelope shape. `meta.contract_version` tracks additive movement inside it.\n\n**Adding a member to any status enum in this document is a BREAKING change, not an additive\none.** Consumers switch on these values and a new member would fall through a default branch,\nrendering a real regulatory state as unremarkable. Every enum here is a closed set; additions\nship with a minor contract_version bump and a `Deprecation` notice, never silently.","contact":{"name":"Med Spa Radar","url":"https://medsparadar.com/api","email":"support@medsparadar.com"},"termsOfService":"https://medsparadar.com/terms","license":{"name":"Proprietary — see Terms of Service","url":"https://medsparadar.com/terms"}},"servers":[{"url":"https://medsparadar.com","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"substances","description":"Federal standing for tracked substances. Free tier."},{"name":"rules","description":"The statute-cited rules corpus. Commercial tier, licensed per state."},{"name":"changes","description":"The change feed. Commercial tier, paged by opaque cursor."},{"name":"states","description":"Per-state coverage posture. Counts and tier only — the cited claims themselves are a commercial tier."},{"name":"meta","description":"Pipeline and corpus freshness. Free tier."}],"paths":{"/v1/substances":{"get":{"operationId":"substances.list","tags":["substances"],"summary":"Every tracked substance with its current federal standing","description":"A substance may be tracked with nothing on record. Those carry `standing_status = \"not_on_record\"` — an absence of record on our side, not a finding that the substance is unremarkable.","responses":{"200":{"description":"Substances, each with its standing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubstanceListEnvelope"}}}}}}},"/v1/substances/{slug}":{"get":{"operationId":"substances.get","tags":["substances"],"summary":"One substance: standing, full published event timeline, and PCAC framing","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"example":"semaglutide"}],"responses":{"200":{"description":"The substance. `record.status` is `not_on_record` when it is tracked but has no published events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"404":{"description":"No substance is tracked under that slug. This is not a statement about the substance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/states/{state}":{"get":{"operationId":"states.get","tags":["states"],"summary":"Coverage posture for one state","description":"Counts and tier only — never the cited claims themselves, which are a commercial tier. Use this to discover whether a state has a corpus worth licensing before buying.","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":2},"example":"TX"}],"responses":{"200":{"description":"Posture. `record.status` is `not_covered` unless a cited corpus exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateEnvelope"}}}},"400":{"description":"Not a recognised US state or territory code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/rules/{state}":{"get":{"operationId":"rules.get","tags":["rules"],"summary":"The statute-cited rules corpus for one state","description":"Commercial tier. A key is licensed for named states; a state outside that scope returns 403\nnaming the states the key does cover.\n\nA state we have not written returns **200** with `record.status = \"not_covered\"`, not a 404.\nThat is deliberate: an absent corpus is a gap in our publication and says nothing about the\nstate's law, and that sentence has to reach the caller rather than sit in a body most HTTP\nclients discard on a 4xx.\n\nDomains with no claims are returned with `claim_count: 0` rather than omitted, so a partially\nwritten state cannot be mistaken for a complete one.\n\nEvery claim carries its own `verification` status. Read that per claim, not the corpus median —\na consultancy citing one claim needs to know whether **that** claim is stale.","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","enum":["AZ","CA","FL","GA","IL","IN","NC","NJ","NY","OH","PA","TX"]},"example":"TX"}],"responses":{"200":{"description":"The corpus, or an explicit not_covered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RulesEnvelope"}}}},"400":{"description":"Not a recognised US state or territory code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"This key is not licensed for that state, or is not a commercial key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/changes":{"get":{"operationId":"changes.list","tags":["changes"],"summary":"What moved, since when","description":"Commercial tier. Page with `next_cursor` — it is opaque and URL-safe.\n\n`since` is compared against when an item was **recorded here**, not the date its source\nassigned. The two differ by a measured week or more, so paging on the source date would\nsilently drop late-arriving items.\n\nAn empty window returns `record.status = \"not_on_record\"` with an explanation. At a measured\ntwo to three material items a month most windows ARE empty, and being able to assert that\nnothing happened is the point of the endpoint — a bare `[]` cannot be told apart from a\nbroken poll.\n\n**Federal items are returned to every key regardless of state scope.** An FDA action applies\neverywhere; withholding it from a state-scoped key would be a hole in the monitoring, not a\nlicensing boundary. Scope governs state-specific material only.\n\nTier C background records are excluded by default. Pass `material_only=false` to include them.","parameters":[{"name":"since","in":"query","schema":{"type":"string"},"description":"A `next_cursor` from a previous response, or an ISO 8601 timestamp."},{"name":"state","in":"query","schema":{"type":"string","minLength":2,"maxLength":2},"description":"Two-letter code. Must be within the key scope."},{"name":"source","in":"query","schema":{"type":"string","enum":["legiscan","fda-compounding","fda-shortage","fda-enforcement","fda-device","fda-warning-letters","fda-import-refusals","fda-inspections","ftc-enforcement"]},"description":"Narrow to one feed. Also scopes the freshness block to that feed."},{"name":"material_only","in":"query","schema":{"type":"boolean","default":true},"description":"Exclude Tier C background records. Default true."},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"Changes, or an explicit not_on_record for a quiet window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangesEnvelope"}}}},"400":{"description":"Malformed cursor, state, or source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"403":{"description":"Not a commercial key, or a state outside the key scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}},"/v1/meta/freshness":{"get":{"operationId":"meta.freshness","tags":["meta"],"summary":"Pipeline liveness, corpus size and verification age","description":"Publishes our own worst numbers on purpose: the oldest unverified claim, the measured publication latency, and how many jurisdictions we have genuinely written.","responses":{"200":{"description":"Freshness across every axis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Free keys are self-serve at https://medsparadar.com/api and allow 60 requests/minute and 5000/day. The open-data endpoints also accept unauthenticated requests, at a lower shared limit; a key gives you your own budget and a contact we can reach before switching anything off. Commercial keys are issued by quote and are scoped to named states."}},"schemas":{"RecordBlock":{"type":"object","required":["status","scope","explanation"],"description":"What kind of answer this is. Read this BEFORE reading `data`.","properties":{"status":{"type":"string","enum":["on_record","not_on_record","not_covered","undetermined"],"description":"Closed set — see the versioning note. `not_covered` is not a clean bill of health."},"scope":{"type":"string","description":"What was asked about, e.g. \"US-TX\"."},"explanation":{"type":"string","description":"Always populated. For anything other than `on_record` this is the load-bearing field."}}},"PipelineBlock":{"type":"object","required":["status","reason","feeding_sources"],"properties":{"status":{"type":"string","enum":["healthy","degraded","unknown"],"description":"`unknown` means we could not read pipeline health. It is never an all-clear."},"reason":{"type":"string","description":"Why this status. Always populated, including for `healthy`."},"last_scan_at":{"type":["string","null"],"description":"Most recent confirmed successful poll across all sources."},"sources_total":{"type":["integer","null"],"description":"Total sources polled."},"sources_stale":{"type":["integer","null"],"description":"How many are outside their freshness window. Never which — see `feeding_sources` for the ones relevant to this endpoint."},"feeding_sources":{"type":"array","description":"Only the sources that feed THIS endpoint. `stale` describes OUR poll, never the upstream service.","items":{"type":"object","properties":{"source_id":{"type":"string","enum":["legiscan","fda-compounding","fda-shortage","fda-enforcement","fda-device","fda-warning-letters","fda-import-refusals","fda-inspections","ftc-enforcement","substance-tracker"]},"stale":{"type":["boolean","null"]}}}}}},"LatencyBlock":{"type":"object","required":["status","note"],"description":"Measured end-to-end publication latency, or an explicit statement that the sample was too small. Never omitted, never estimated.","properties":{"status":{"type":"string","enum":["measured","insufficient_sample"]},"median_days":{"type":"integer","description":"Median days from a source dating an item to it going live here."},"p25_days":{"type":"integer","description":"25th percentile."},"p75_days":{"type":"integer","description":"75th percentile."},"within_week_pct":{"type":"integer","description":"Share live within seven days, whole percent."},"sample":{"type":"integer","description":"Items in the window."},"window_days":{"type":"integer","description":"Ingest window the figure was computed over."},"note":{"type":"string","description":"The caveat travels with the figure. This is not a pipeline latency."}}},"CorpusBlock":{"type":"object","description":"Verification age of the cited rules corpus.","properties":{"total_claims":{"type":"integer","description":"Cited claims in the corpus."},"fresh":{"type":"integer","description":"Verified inside the ceiling."},"due_soon":{"type":"integer","description":"Scheduled for re-reading."},"stale":{"type":"integer","description":"Past the ceiling, or carrying an unparseable verification date."},"oldest_claim_days":{"type":["integer","null"],"description":"Age of the OLDEST claim — the corpus worst case, not its average."},"median_claim_days":{"type":["integer","null"],"description":"Median claim age in days."},"ceiling_days":{"type":"integer","description":"Days after which a claim's verification is treated as expired. Currently 180."},"has_stale":{"type":"boolean","description":"True when anything is past the ceiling."}}},"FreshnessBlock":{"type":"object","required":["as_of","pipeline","publication_latency"],"properties":{"as_of":{"type":"string","description":"When this response was computed.","format":"date-time"},"pipeline":{"$ref":"#/components/schemas/PipelineBlock"},"content":{"type":"object","description":"Age of the newest item behind this response, where the endpoint has one.","properties":{"latest_date":{"type":["string","null"],"description":"Newest item date."},"age_days":{"type":["integer","null"],"description":"Whole days since."}}},"corpus":{"$ref":"#/components/schemas/CorpusBlock"},"publication_latency":{"$ref":"#/components/schemas/LatencyBlock"}}},"AttributionEntry":{"type":"object","required":["source_id","publisher","license","statement","applies_to"],"description":"Derived from the items actually emitted. Empty when nothing attributable was returned.","properties":{"source_id":{"type":"string","enum":["legiscan","fda-compounding","fda-shortage","fda-enforcement","fda-device","fda-warning-letters","fda-import-refusals","fda-inspections","ftc-enforcement","substance-tracker"]},"publisher":{"type":"string","description":"The publishing body."},"license":{"type":"string","description":"Licence the data is redistributed under."},"license_url":{"type":["string","null"],"description":"Canonical licence URL."},"statement":{"type":"string","description":"The EXACT attribution that must travel with this data if you pass it on."},"applies_to":{"type":"array","items":{"type":"string"},"description":"The item ids this entry covers, so a consumer redistributing a subset knows which rows carry the obligation."}}},"LicenseBlock":{"type":"object","required":["redistribution","attribution_required","terms_url","not_legal_advice"],"description":"The terms this response was served under, echoed in-band so a redistributed artifact carries them.","properties":{"redistribution":{"type":"string","enum":["internal_only","client_deliverables"]},"licensed_to":{"type":["string","null"],"description":"The licensed organisation, where the key names one."},"attribution_required":{"type":"boolean"},"terms_url":{"type":"string","description":"Terms of Service."},"not_legal_advice":{"type":"string","description":"Carried in-band because an API response has no surrounding UI to carry it."}}},"MetaBlock":{"type":"object","required":["contract_version","endpoint","generated_at"],"properties":{"contract_version":{"type":"string","description":"Additive movement inside the v1 envelope."},"endpoint":{"type":"string","description":"Stable endpoint id."},"generated_at":{"type":"string","description":"","format":"date-time"},"cursor":{"type":"object","properties":{"next":{"type":["string","null"],"description":"Opaque cursor."},"has_more":{"type":"boolean"}}}}},"ErrorBlock":{"type":"object","description":"Present only on failures. `record.status` is `undetermined` alongside it.","properties":{"code":{"type":"string","enum":["unauthorized","forbidden","upgrade_required","out_of_scope","not_found","bad_request","rate_limited","internal"]},"message":{"type":"string","description":"Human-readable."},"detail":{"type":["object","null"],"description":"Machine-usable context, e.g. which states your key does cover."}}},"Envelope":{"type":"object","required":["data","record","freshness","attribution","license","meta"],"description":"Every response, success or failure, has this shape.","properties":{"data":{"description":"The resource, or null on failure."},"record":{"$ref":"#/components/schemas/RecordBlock"},"freshness":{"$ref":"#/components/schemas/FreshnessBlock"},"attribution":{"type":"array","items":{"$ref":"#/components/schemas/AttributionEntry"}},"license":{"$ref":"#/components/schemas/LicenseBlock"},"meta":{"$ref":"#/components/schemas/MetaBlock"},"error":{"$ref":"#/components/schemas/ErrorBlock"}}},"Standing":{"type":"object","properties":{"id":{"type":"string","description":"Stable within this response; referenced by `attribution[].applies_to`."},"dimension":{"type":"string","description":"Which axis of standing, e.g. 503a_category, pcac_event."},"jurisdiction":{"type":"string","description":"Currently federal for all published standing."},"status_value":{"type":"string","description":"The controlled value as published by the source. SOURCED."},"status_label":{"type":"string","description":"Our plain-language translation. MSR-ORIGINAL — do not attribute it to the source."},"event_date":{"type":["string","null"],"description":"Date the source assigned."},"summary":{"type":["string","null"],"description":"Source summary."},"source_doc_url":{"type":["string","null"],"description":"The primary document."},"source_doc_title":{"type":["string","null"],"description":""},"citation_ref":{"type":["string","null"],"description":""},"provenance":{"type":"string","enum":["sourced","msr_analysis"]},"source_id":{"type":"string","description":"Present iff provenance is `sourced`."}}},"Substance":{"type":"object","properties":{"slug":{"type":"string","description":""},"common_name":{"type":"string","description":""},"inn_name":{"type":["string","null"],"description":""},"aliases":{"type":"array","items":{"type":"string"}},"substance_class":{"type":"string","description":""},"class_label":{"type":"string","description":"MSR-original label."},"standing_status":{"type":"string","enum":["on_record","not_on_record"],"description":"Per-substance absence typing. Moves together with `standing`."},"standing_note":{"type":"string","description":"Why the standing is what it is. Load-bearing when `not_on_record`."},"standing":{"type":"array","items":{"$ref":"#/components/schemas/Standing"}}}},"SubstanceListEnvelope":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Substance"}}}}]},"DomainCoverage":{"type":"object","description":"A domain of The Practice Perimeter. This is the framework the product already runs on — not a taxonomy coined for the API.","properties":{"key":{"type":"string","enum":["ownership_cpom","supervision_delegation","good_faith_exam","rn_np_scope","medspa_registration","advertising","disciplinary"]},"title":{"type":"string","description":""},"short":{"type":"string","description":""},"question":{"type":"string","description":"The question an operator asks. Never a rule."},"cited_claims":{"type":"integer","description":"Cited claims we hold in this domain for this state."}}},"StatePosture":{"type":"object","properties":{"state":{"type":"string","description":"Two-letter code."},"state_name":{"type":"string","description":""},"tier":{"type":"string","enum":["covered","monitored","thin"],"description":"`covered` means a cited corpus exists."},"coverage_note":{"type":"string","description":"What our coverage does and does not mean for this state."},"cited_claims":{"type":"integer","description":"Claims in the corpus for this state. Zero outside covered states."},"answerable_questions":{"type":"integer","description":"Taxonomy questions answerable from the cited record."},"material_changes":{"type":["integer","null"],"description":"Material state-specific changes on record. NULL means the count could not be TAKEN — which is not zero, and must not be rendered as zero."},"latest_change_date":{"type":["string","null"],"description":"Most recent material change. Background exhaust is excluded."},"domains_total":{"type":"integer","description":"Domains in the framework."},"domains_covered":{"type":"array","items":{"$ref":"#/components/schemas/DomainCoverage"}},"corpus_verified":{"type":["object","null"],"properties":{"oldest_claim_days":{"type":["integer","null"],"description":""},"stale_claims":{"type":"integer","description":""},"due_soon_claims":{"type":"integer","description":""},"state_updated_at":{"type":["string","null"],"description":""}}},"rules_endpoint":{"type":["object","null"],"description":"Where the claims live, and what it takes to read them. Null unless a corpus exists."},"provenance":{"type":"string","enum":["sourced","msr_analysis"]}}},"CitedClaim":{"type":"object","description":"One statute-cited claim. MSR-original: the STATEMENT is our reading of the instrument, in our words, which is why provenance is msr_analysis and no upstream attribution attaches. The citation and source_url are how you check us.","properties":{"id":{"type":"string","description":"Stable within this response."},"statement":{"type":"string","description":"What the rule requires, in plain language. MSR-original."},"citation":{"type":"string","description":"Exact primary-source citation, e.g. \"22 TAC §169.25\"."},"source_url":{"type":"string","description":"The primary-source URL the statement was verified against."},"verification":{"type":"object","description":"Per-claim, not a corpus average. Read this before citing the claim.","properties":{"as_of":{"type":"string","description":"Date the citation was last confirmed."},"age_days":{"type":["integer","null"],"description":"Whole days since. Null when the date will not parse."},"status":{"type":"string","enum":["fresh","due_soon","stale","unparseable"],"description":"A FUTURE as_of reports `unparseable`, never \"very fresh\" — the worst typo is the one that makes a claim look newly verified."},"ceiling_days":{"type":"integer","description":"Days after which verification is treated as expired."},"note":{"type":"string","description":"What this status means for relying on the claim."}}},"provenance":{"type":"string","enum":["sourced","msr_analysis"]}}},"RuleSection":{"type":"object","properties":{"key":{"type":"string","enum":["ownership_cpom","supervision_delegation","good_faith_exam","rn_np_scope","medspa_registration","advertising","disciplinary"]},"title":{"type":"string","description":""},"domain":{"type":["object","null"],"description":"The Practice Perimeter domain this section belongs to."},"summary":{"type":["string","null"],"description":""},"claim_count":{"type":"integer","description":"Zero means the domain is unwritten for this state — a gap in our publication, not a finding that no requirement exists."},"claims":{"type":"array","items":{"$ref":"#/components/schemas/CitedClaim"}}}},"StateRules":{"type":"object","properties":{"state":{"type":"string","description":""},"state_name":{"type":"string","description":""},"corpus_updated_at":{"type":"string","description":"When this state was last touched as a whole."},"total_claims":{"type":"integer","description":""},"sections":{"type":"array","items":{"$ref":"#/components/schemas/RuleSection"}},"domains_total":{"type":"integer","description":""},"domains_with_claims":{"type":"integer","description":""},"coverage_note":{"type":"string","description":"States plainly what an unwritten domain does and does not mean."}}},"RulesEnvelope":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/StateRules"},{"type":"null"}]}}}]},"ChangeItem":{"type":"object","properties":{"id":{"type":"string","description":""},"title":{"type":["string","null"],"description":""},"summary":{"type":["string","null"],"description":""},"state":{"type":["string","null"],"description":"Two-letter code, or null for a federal item."},"jurisdiction":{"type":"string","enum":["federal","state"]},"source_category":{"type":["string","null"],"description":""},"source_id":{"type":"string","description":"The registered feed, when the category maps to one."},"source_url":{"type":["string","null"],"description":"The upstream document."},"published_date":{"type":["string","null"],"description":"The date the SOURCE assigned. May lag recorded_at by weeks."},"recorded_at":{"type":"string","description":"When the item appeared here. THIS is what the cursor pages on.","format":"date-time"},"materiality":{"type":"string","enum":["A","B","C"],"description":"MSR-original classification, not the source’s."},"materiality_note":{"type":"string","description":"What the tier means."},"provenance":{"type":"string","enum":["sourced","msr_analysis"]}}},"ChangesEnvelope":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ChangeItem"}}}}]},"StateEnvelope":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/StatePosture"}}}]}}}}