openapi: 3.1.0
info:
title: Aerin Medical Site API (WordPress REST)
version: em-locator/v1 + aerin + wp/v2
summary: Anonymously callable REST surface served by aerinmedical.com — the ENT doctor
locator (1,012 treating locations), site search, and the site's form-submission endpoints.
description: 'Aerin Medical is a medical-device company and operates no developer API
product: it publishes no developer portal, no API documentation, no keys, no SDKs and
no terms of use for programmatic access.
Its corporate site, aerinmedical.com, does however serve a WordPress REST API at
`https://aerinmedical.com/wp-json` with 321 registered routes across 15 namespaces. Most
of the standard `wp/v2` content routes (posts, pages, media, users, taxonomies, types)
are BLOCKED to anonymous callers by the iThemes Security plugin and return HTTP 401
`itsec_rest_api_access_restricted` — so, unlike most WordPress sites, the content
library here is not machine-readable. What IS anonymously readable is the company''s
own first-party doctor-finder plugin (`em-locator/v1`, the surface behind
https://aerinmedical.com/find-ent-doctor/), the cross-content `wp/v2/search` index, the
route discovery documents, and a small set of `aerin`-namespace POST endpoints that
back the site''s lead-capture and NOSE-score assessment forms.
This document was DERIVED by API Evangelist from the provider''s own live route index
(`GET https://aerinmedical.com/wp-json/`) plus direct anonymous probing of each route,
on 2026-07-31. Every path, method, parameter name, enumeration and response field is
taken from that index or from an observed HTTP 200 response; nothing was invented.
Routes observed to return 401 anonymously are deliberately omitted. The `aerin`
namespace POST routes are listed because they are really registered and really public,
but they declare NO argument schema in the route index and they have side effects
(they submit leads / contact requests / newsletter sign-ups into the company''s
systems), so they were NOT invoked and no request body is documented for them.
Treat this as an incidental site surface, not a supported product API. Aerin Medical
offers no availability, versioning or support commitment for it.'
contact:
name: Aerin Medical, Inc.
url: https://aerinmedical.com/contact-us/
email: customerservice@aerinmedical.com
x-origin:
- url: https://aerinmedical.com/wp-json/
format: wordpress-rest-route-index
version: wp/v2
x-apievangelist-method: derived
x-apievangelist-derived-from: https://aerinmedical.com/wp-json/ (live route index + per-route
anonymous probes, fetched 2026-07-31)
x-apievangelist-note: Incidental site API, not a product API. Derived by API Evangelist,
not published by Aerin Medical.
servers:
- url: https://aerinmedical.com/wp-json
description: aerinmedical.com WordPress REST API
tags:
- name: Discovery
description: Route index / discovery documents.
- name: Locator
description: The Aerin doctor finder — treating ENT locations, filterable by product,
designation, text and geography.
- name: Search
description: Cross-content-type site search.
- name: Forms
description: Site form-submission endpoints (lead capture, NOSE score assessments).
Side-effecting; not invoked during derivation.
- name: Embed
description: oEmbed representations of aerinmedical.com URLs.
paths:
/:
get:
tags: [Discovery]
operationId: getRouteIndex
summary: Get the WordPress REST route index
description: Returns the site's discovery document — name, home URL, the 15 registered
namespaces, all 321 routes with their methods and declared arguments, and the
supported authentication method (WordPress application passwords).
responses:
'200':
description: The route index.
content:
application/json:
schema:
$ref: '#/components/schemas/RouteIndex'
/aerin:
get:
tags: [Discovery]
operationId: getAerinNamespaceIndex
summary: Get the aerin namespace index
description: Returns the route index scoped to Aerin Medical's own `aerin` namespace —
the site's seven form-handling POST routes.
parameters:
- name: namespace
in: query
required: false
schema: {type: string, default: aerin}
- name: context
in: query
required: false
schema: {type: string, default: view}
responses:
'200':
description: The namespace index.
content:
application/json:
schema:
$ref: '#/components/schemas/RouteIndex'
/em-locator/v1:
get:
tags: [Discovery]
operationId: getLocatorNamespaceIndex
summary: Get the em-locator namespace index
responses:
'200':
description: The namespace index.
content:
application/json:
schema:
$ref: '#/components/schemas/RouteIndex'
/em-locator/v1/locations:
get:
tags: [Locator]
operationId: listTreatingLocations
summary: List ENT locations trained on Aerin procedures
description: 'Returns the treating locations behind https://aerinmedical.com/find-ent-doctor/ —
1,012 records as observed on 2026-07-31. Each record carries the practice name,
formatted address, phone, latitude/longitude, the public location permalink, and
pre-rendered HTML fragments used by the site''s map and result list.
Pagination is standard WordPress: 10 records per page by default, with the total
count in the `X-WP-Total` response header and the page count in `X-WP-TotalPages`.
Supplying `lat` and `lng` switches the collection into proximity mode — results are
ordered by distance and each record''s `distance` field becomes a number (miles)
instead of `false`.
The route index declares no arguments for this route; every parameter below was
confirmed by observed behaviour (differing `X-WP-Total` / ordering / `distance`
values) against the live endpoint. `product` and `designation` take the WordPress
term IDs exposed by the locator form at `GET /em-locator/v1/locator`.
CORS is open (`Access-Control-Allow-Origin: *`) and responses are cached
(`Cache-Control: max-age=600, must-revalidate`).'
parameters:
- name: page
in: query
description: Page of the collection. Observed 102 pages at 10 per page.
required: false
schema: {type: integer, minimum: 1, default: 1}
- name: per_page
in: query
description: Records per page.
required: false
schema: {type: integer, default: 10}
- name: search
in: query
description: Free-text filter over practice name / city / state. Observed to narrow
`X-WP-Total` (e.g. `search=Austin` → 44).
required: false
schema: {type: string}
- name: lat
in: query
description: Latitude of the search origin. Supply together with `lng` to sort by
proximity and populate `distance`.
required: false
schema: {type: number, format: double}
- name: lng
in: query
description: Longitude of the search origin. Supply together with `lat`.
required: false
schema: {type: number, format: double}
- name: product
in: query
description: Filter to locations offering one product, by term ID. Observed values —
`697` RhinAer (783 locations), `698` VivAer (920 locations).
required: false
schema:
type: string
enum: ['697', '698']
x-enum-descriptions: {'697': RhinAer, '698': VivAer}
- name: designation
in: query
description: Filter to Aerin Center of Excellence designations, by term ID or the
`center-of-excellence` roll-up. Observed counts — any Center of Excellence 60,
`699` Premier VivAer 17, `700` Advanced RhinAer 22, `701` Advanced VivAer 30,
`702` Premier RhinAer 5.
required: false
schema:
type: string
enum: [center-of-excellence, '699', '700', '701', '702']
x-enum-descriptions:
center-of-excellence: Any Center of Excellence
'699': Premier VivAer
'700': Advanced RhinAer
'701': Advanced VivAer
'702': Premier RhinAer
responses:
'200':
description: 'A page of treating locations. `X-WP-Total` carries the full match count;
an out-of-range page returns an empty array with `X-WP-Total: 0`.'
headers:
X-WP-Total:
description: Total records matching the query.
schema: {type: integer}
X-WP-TotalPages:
description: Total pages available.
schema: {type: integer}
Access-Control-Allow-Origin:
description: Observed as `*`.
schema: {type: string}
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Location'
/em-locator/v1/locator:
get:
tags: [Locator]
operationId: getLocatorWidget
summary: Get the rendered doctor-finder widget
description: Returns the server-rendered HTML for the doctor-finder search widget,
including the product and designation filter `<select>` elements whose option values
are the term IDs accepted by `listTreatingLocations`.
responses:
'200':
description: The rendered widget.
content:
application/json:
schema:
type: object
properties:
html:
type: string
description: Server-rendered HTML for the locator search form and map shell.
/custom-footer/v1/get-custom-footer-html:
get:
tags: [Discovery]
operationId: getCustomFooterHtml
summary: Get the rendered site footer
description: Returns the site's shared footer markup. Used by the theme; exposed
anonymously.
responses:
'200':
description: The rendered footer.
content:
application/json:
schema:
type: object
/wp/v2/search:
get:
tags: [Search]
operationId: searchSite
summary: Search across aerinmedical.com content
description: 'Cross-content-type search. 2,242 items were indexed as observed on
2026-07-31. This is the ONLY `wp/v2` collection reachable anonymously — `posts`,
`pages`, `media`, `categories`, `tags`, `users`, `comments`, `types`, `taxonomies`
and `statuses` all return 401 `itsec_rest_api_access_restricted`, and the `_links.self`
hrefs in each result therefore also 401. Results carry `id`, `title`, `url`, `type`
and `subtype` only.
All parameters below are declared verbatim in the site''s own route index.'
parameters:
- name: context
in: query
description: Scope under which the request is made; determines fields present in response.
required: false
schema: {type: string, enum: [view, embed], default: view}
- name: page
in: query
description: Current page of the collection.
required: false
schema: {type: integer, minimum: 1, default: 1}
- name: per_page
in: query
description: Maximum number of items to be returned in result set.
required: false
schema: {type: integer, minimum: 1, maximum: 100, default: 10}
- name: search
in: query
description: Limit results to those matching a string.
required: false
schema: {type: string}
- name: type
in: query
description: Limit results to items of an object type.
required: false
schema: {type: string, enum: [post, term, post-format], default: post}
- name: subtype
in: query
description: 'Limit results to items of one or more object subtypes. Note the
Aerin-specific custom post types: `location`, `physician`, `alert`, and the
`em_designation` taxonomy.'
required: false
schema:
type: array
default: [any]
items:
type: string
enum: [post, page, location, physician, alert, category, post_tag, em_designation, any]
- name: exclude
in: query
description: Ensure result set excludes specific IDs.
required: false
schema: {type: array, default: [], items: {type: integer}}
- name: include
in: query
description: Limit result set to specific IDs.
required: false
schema: {type: array, default: [], items: {type: integer}}
responses:
'200':
description: A page of search results.
headers:
X-WP-Total:
description: Total items matching the query.
schema: {type: integer}
X-WP-TotalPages:
description: Total pages available.
schema: {type: integer}
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SearchResult'
/oembed/1.0/embed:
get:
tags: [Embed]
operationId: getOEmbed
summary: Get an oEmbed representation of an aerinmedical.com URL
parameters:
- name: url
in: query
required: true
description: The URL of the resource for which to fetch oEmbed data.
schema: {type: string, format: uri}
- name: format
in: query
required: false
schema: {type: string, enum: [json, xml], default: json}
- name: maxwidth
in: query
required: false
schema: {type: integer, default: 600}
responses:
'200':
description: The oEmbed response.
content:
application/json:
schema:
type: object
/aerin/locations:
post:
tags: [Forms]
operationId: submitLocationsFormQuery
summary: Submit a doctor-finder query from the site form
description: Registered POST route backing the doctor-finder page. NOT INVOKED during
derivation — the route index declares no arguments and the endpoint has side effects,
so no request body is documented. Use `listTreatingLocations` for read access.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/google:
post:
tags: [Forms]
operationId: submitGoogleProxyRequest
summary: Server-side proxy used by the locator's Google Maps integration
description: Registered POST route in the `aerin` namespace. NOT INVOKED during
derivation; no argument schema is declared or documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/contact:
post:
tags: [Forms]
operationId: submitContactForm
summary: Submit the site contact form
description: Registered POST route backing https://aerinmedical.com/contact-us/.
Side-effecting (creates a contact record); NOT INVOKED during derivation and no
request body is documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/newsletter:
post:
tags: [Forms]
operationId: submitNewsletterSignup
summary: Submit a newsletter sign-up
description: Registered POST route backing the site's e-mail sign-up. Side-effecting;
NOT INVOKED during derivation and no request body is documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/request-evaluation:
post:
tags: [Forms]
operationId: submitEvaluationRequest
summary: Submit a request to be contacted for an evaluation
description: Registered POST route backing the site's patient evaluation request flow.
Side-effecting (creates a patient lead); NOT INVOKED during derivation and no request
body is documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/nose-score:
post:
tags: [Forms]
operationId: submitNoseScore
summary: Submit a NOSE (Nasal Obstruction Symptom Evaluation) assessment
description: Registered POST route backing the site's nasal-obstruction self-assessment.
Side-effecting and potentially collects health-related answers; NOT INVOKED during
derivation and no request body is documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
/aerin/rhinitis-nose-score:
post:
tags: [Forms]
operationId: submitRhinitisNoseScore
summary: Submit a chronic-rhinitis symptom self-assessment
description: Registered POST route backing the site's chronic-rhinitis self-assessment.
Side-effecting and potentially collects health-related answers; NOT INVOKED during
derivation and no request body is documented.
x-apievangelist-undeclared-request-body: true
responses:
default:
description: Not observed — endpoint was not invoked.
components:
securitySchemes:
applicationPassword:
type: http
scheme: basic
description: 'WordPress application passwords, advertised by the site''s own route index
at `authentication.application-passwords.endpoints.authorization` =
https://aerinmedical.com/wp-admin/authorize-application.php. This is the built-in
WordPress mechanism for authenticated (administrative) calls; Aerin Medical issues no
public credentials, and every operation in this document is reachable ANONYMOUSLY
with no credential at all.'
schemas:
RouteIndex:
type: object
description: A WordPress REST route-discovery document.
properties:
name: {type: string}
description: {type: string}
url: {type: string, format: uri}
home: {type: string, format: uri}
namespaces:
type: array
items: {type: string}
authentication: {type: object}
routes:
type: object
description: Map of route pattern to its namespace, methods, endpoints and declared args.
_links: {type: object}
Location:
type: object
description: One ENT practice location trained on Aerin procedures.
properties:
ID:
type: integer
description: WordPress post ID of the location.
address:
type: string
description: Street address as entered.
formatted_address:
type: string
description: Geocoded, display-formatted address.
lat:
type: string
description: Latitude, as a string.
lng:
type: string
description: Longitude, as a string.
distance:
oneOf:
- {type: boolean}
- {type: number}
description: '`false` unless `lat`/`lng` were supplied, in which case the distance
from the search origin in the units given by `plugin_settings.distance_unit`.'
phone:
type: string
description: Practice phone number, formatted for display.
email:
type: string
description: Contact e-mail. Observed empty on every sampled record.
website:
type: string
description: Practice website. Observed empty on every sampled record.
permalink:
type: string
format: uri
description: Public page for this location on aerinmedical.com.
list_item_html:
type: string
description: Pre-rendered `<li>` fragment for the result list, including the product
badges (RhinAer / VivAer) shown on the site.
map_item_html:
type: string
description: Pre-rendered map marker fragment.
map_details_html:
type: string
description: Pre-rendered map info-window fragment.
plugin_settings:
type: object
description: 'Locator plugin configuration echoed on every record — map centre, zoom,
distance unit, page size, labels. NOTE: as observed on 2026-07-31 this object also
echoes the site''s Google Maps browser API key and empty basic-auth fields to every
anonymous caller. Recorded as an observation only; the value is deliberately not
reproduced in this repository. See security/aerin-medical-domain-security.yml.'
post:
type: object
description: 'The raw underlying WordPress post record. NOTE: as observed on
2026-07-31 its `post_content` is a JSON blob carrying internal CRM fields —
Salesforce id, account number and the named sales representative for the account —
alongside the public address and product flags. Recorded as an observation; values
are deliberately not reproduced here.'
properties:
ID: {type: integer}
post_title: {type: string}
post_name: {type: string}
post_type: {type: string, const: location}
post_status: {type: string}
post_date: {type: string}
post_modified: {type: string}
post_parent: {type: integer}
post_content:
type: string
description: JSON string with address, city, state, zip, country and the
`rhinaer` / `vivaer` / `inoffice` / `excellence` product flags — plus internal
CRM fields (see note above).
SearchResult:
type: object
properties:
id: {type: integer}
title: {type: string}
url: {type: string, format: uri}
type: {type: string}
subtype: {type: string}
_links:
type: object
description: Hypermedia links. The `self` href points at a `wp/v2` collection that
returns 401 to anonymous callers.
Error:
type: object
description: The WordPress REST error envelope.
properties:
code: {type: string}
message: {type: string}
data:
type: object
properties:
status: {type: integer}