A field reference for PolyAI voice agents integrating with Planet DDS Cloud 9 Ortho. Built strictly from Planet DDS's own documentation — the Basic Guide and Commonly Used APIs (12.10), the Allowable APIs During Production Hours list, the New Patient Online Scheduling Process, and the 2026 API Pricing schedule. Where the documented API cannot complete a flow, this guide says so plainly, and sets out what we do instead.
Cloud 9 is not a FHIR server and shares no surface with Epic, Cerner or AthenaOne. Planet DDS describe their own API as “custom (SOAPish) XML” — a single handler that dispatches on a <Procedure> element inside the request body. Three properties of that design shape the whole integration.
Every one of the ~100 operations is an HTTP POST to GetData.ashx. The operation is named in the body, not the path. There is no REST resource model, no HTTP verbs beyond POST, and no status-code semantics — errors come back 200 OK with an <ErrorCode>.
Auth is a ClientID GUID plus a static UserName / Password, sent in the request body on every call. No OAuth, no bearer tokens, no expiry, no refresh. One credential set per client database.
The full API is available 00:00–11:00 UTC. Outside that, Planet DDS publishes a standing allowable-APIs list for production hours, each procedure with its own call interval. Every write these flows need is on it.
For an established patient, five procedures carry the whole flow. Two of them are the join that Cloud 9 does not make for you.
GetPortalPatientLookupResolve caller → PatientGUID. Filter accepts last name, first name or Patient ID — not a phone number. Returns PatientBirthDate for the DOB verifier.
GetPatientRelationshipContactInfoPer-patient contact rows. This is how the third identity point (phone) is verified once you already hold a patGUID.
GetOnlineReservationsBookable slots from the office's schedule templates. Capped at a 28-week range; only appointment types flagged Allow Online Scheduling in Setup are returned.
Planet DDS confirms that StartTime, ScheduleView, ScheduleColumn and Minutes all come straight from the chosen reservation — no separate column lookup is needed.
SetAppointmentWrites the appointment. Returns Appointment GUID Added: apptGUID. Rejects past-dated times and slots the template will not accept.
| Capability | Cloud 9 procedure(s) | Status |
|---|---|---|
| Patient identification | GetPortalPatientLookup | Name / ID only — no phone |
| Identity verification (3rd point) | GetPatientRelationshipContactInfo | Supported |
| Locate appointments | GetPatientAppointments | Supported |
| Slot availability | GetOnlineReservations | 28-week cap · 15s interval |
| Book | SetAppointment | Supported — join confirmed |
| Cancel | SetAppointmentStatusCanceled | Supported — no reason field |
| Confirm | SetAppointmentStatusConfirmed | Supported |
| Reschedule | — none — | No native operation |
| Create patient | SetPatient · SetPersonRelationship | Hard DB preconditions |
| Coverage on file (read) | GetInsurancePoliciesForPatient | Stored record — not eligibility |
| Real-time eligibility (270/271) | — none on Cloud 9 — | Denticon-only product |
| Reverse phone lookup | — none — | Cache + local index |
| Payment capture | — none — | No Set procedure exists |
Cloud 9's constraints — a closed daytime window, no push events, no phone-indexed lookup, no atomic reschedule — are exactly the constraints that a thin “call the EHR from the turn” integration handles badly. Agent Studio carries three things that make it workable:
Nightly deltas via GetAppointmentListSince and GetPatientList(ModifiedSince) land in a store the agent reads inside the turn — no live dip needed for identification.
The cancel-then-book reschedule needs a real failure branch, not a prompt that hopes. Studio steps make the compensating path explicit and testable.
GetLocations returns a per-location TimeZone. Variants re-scope the agent's date maths and hours to the resolved location rather than a single global assumption.
Planet DDS put it plainly: “There are two windows for APIs, overnight and daytime.” The full API is available overnight; during the day a published allowable-APIs list applies, each procedure with its own call interval. Which window a procedure sits in determines the whole architecture.
| Window | UTC | What is available | Used for |
|---|---|---|---|
| Overnight in-window | 00:00 – 11:00 | The full API surface | The overnight sync — bulk extracts, reference data, the reverse-phone index |
| Daytime out-of-window · “production hours” | 11:00 – 24:00 | Planet DDS's published allowable-APIs list, each at a defined call interval | Everything the agent does live while a caller is on the phone |
Every allowable procedure carries a frequency, written overnight / daytime in seconds — the minimum interval between calls. These are the real operating constraint; monthly volume rarely binds first.
| Procedure | Interval | Bearing on the agent |
|---|---|---|
SetAppointment · SetPatient · cancel · confirm | 5 / 5 | Every write in these flows. Five seconds is comfortable for a single conversation |
GetPortalPatientLookup · GetPatientAppointments · GetPatientRelationshipContactInfo | 5 / 5 | Identification and appointment lookup |
GetOnlineReservations | 15 / 15 | Slot search. Widening a search re-calls this — budget 15s between attempts and fill conversationally |
SetPersonRelationship | 15 / 30 | Guardian linking. Thirty seconds in daytime — the slowest step in new-patient registration |
SetPatientComment · GetInsurancePoliciesForPatient | 1 / 1 | Effectively unthrottled |
GetNextVisits | 15 / 800 | Over 13 minutes between daytime calls — unusable in a live conversation, and not used here |
GetIsFamilyPartyList | 15 / 300 | Five minutes in daytime. Read it from the overnight sync instead of live |
GetPortalPatientLookup once per conversation. At a 5-second interval that is 12 identifications per minute — 720 per hour. If the interval is scoped per client database, that is the hard ceiling for every caller sharing that database, no matter how many locations or phone lines sit behind it.| Scenario | Peak identifications/min | Against a 12/min ceiling |
|---|---|---|
| Single practice, busy Monday morning | ~2–4 | Comfortable |
| Small group, 5–10 locations, one database | ~5–8 | Workable — queues at peak |
| DSO, 25+ locations, one database | 15–30+ | Exceeds the ceiling |
| DSO, one database per location | 12/min each | Scales cleanly |
Intervals are per procedure, so identification does not contend with slot search or booking — each has its own bucket. Identification is the binding one because every conversation needs it. Note too that Planet DDS state the published frequencies are defaults, finalised “once vendor testing has been completed, based on service needs” — so there is a documented route to negotiating them with evidence.
Four procedures used here sit outside the production-hours list. All four are bulk or reference reads — the kind of data that should be cached in any case.
| Procedure | Why it is synced |
|---|---|
GetPatientPhone | Returns every patient's numbers with no phone-side filter. Builds the reverse-phone index that makes caller-ID recognition possible |
GetPatientList ModifiedSince | Incremental patient delta for the local index |
GetPostalCodes | Postal-code GUIDs for registration. If a caller's code is absent, Planet DDS's guidance is to leave the GUID blank and send city/state/zip — so a stale cache degrades gracefully |
GetPatientStatuses · GetContactInfoTypes | Pre-flight assertions for SetPatient: a status coded NEW and a contact type described Home must both exist |
GetAppointmentListSince, GetLocations, GetAppointmentTypes, GetScheduleViewList and GetEmployees are all also allowable during production hours, so they are cached for speed rather than necessity — and can be re-read live if a variant needs to refresh mid-day.
Daytime access and SET procedures are granted through the Integration Agreement with Planet DDS — a defined contractual step rather than an open-ended approval.
Planet DDS's New Patient Online Scheduling Process names SetAppointment and SetPatient as required, plus SetPatientDemographicInfo, SetPerson, SetPersonDemographicInfo and SetPersonRelationship. These must be enabled before testing, not just before production.
The published intervals are defaults; Planet DDS states they are finalised “once vendor testing has been completed, based on service needs.” There is room to discuss them with evidence.
Procedures outside the allowable list “must go through an approval process and are not guaranteed.” None of the flows in this guide depend on one.
The allowable list is published against the 12.5 release; the API guide is 12.10. Request the current list at contract time and re-check it each release.
The agent performs administrative patient-access work. It does not assess symptoms, judge urgency, or decide what care a patient needs — and it never adjudicates coverage.
Every procedure this guide uses is scheduling, demographic or financial. Cloud 9's clinical surface — treatment cards, charting, imaging — is not touched by any flow here. That is a deliberate boundary, and it happens to be easy to hold in Cloud 9 because the documented API exposes very little clinical data in the first place.
| The agent does | The agent does not |
|---|---|
| Resolve who is calling against existing records | Interpret a clinical complaint or rank its urgency |
| Map a stated reason to a configured appointment type | Decide what treatment the patient needs |
| Read the coverage already on file and report it | Adjudicate, approve or deny coverage |
| Offer slots the practice has flagged bookable | Create availability, override templates or double-book |
| Write appointments, cancellations and confirmations | Write clinical notes, charting or treatment plans |
Every booking flow is fronted by a Safety-Net step that screens for emergency cues before anything else happens. The cue set is authored and approved by the customer's clinical leadership, not by PolyAI, and is deliberately tuned to over-escalate.
On a red flag the agent stops, gives the customer-approved emergency instruction, and transfers to a human. It does not continue to booking, and it does not record a clinical impression.
This distinction matters more in Cloud 9 than in the other EHRs, because Cloud 9 gives you data that looks like an eligibility answer and is not one.
GetInsurancePoliciesForPatient isThe practice's stored record of the patient's policy: carrier, billing centre, policy and group number, subscriber, and the Emdeon payer ID. Written by staff, accurate as of whenever they last touched it.
GetPatientInsurancePolicies addsBenefit fields including pipIndividualOrthoMax, pipLifetimeMax and pipIndividualOrthoMaxRemaining. These are Cloud 9's own running totals — useful, and not a payer response.
There is no 270/271 transaction anywhere in the Cloud 9 API. No procedure contacts a payer. SetAppointmentInsuranceVerified ticks a checkbox on the appointment bubble — it verifies nothing and calls nobody.
Orthodontics skews heavily paediatric, so the caller is frequently not the patient. Cloud 9 models this well and the agent should use it rather than improvising.
| Need | Procedure | Note |
|---|---|---|
| Who may act for this patient | GetPatientRelationshipContactInfo | Returns Relationship, FullName, persGUID per contact row |
| Who is financially responsible | GetResponsiblePartiesForPatient | Only relationships flagged financially responsible |
| Family grouping | GetIsFamilyPartyList | Responsible parties flagged Is Family |
| Link a new relationship | SetPersonRelationship | A patient must have at least one responsible party. The FRP flag cannot be turned off via API. |
| Artefact | Owner | Sign-off |
|---|---|---|
| Emergency cue list & escalation script | Customer clinical leadership | Written approval before go-live |
| Appointment-type mapping (reason → type) | Customer practice operations | Reviewed per location |
| Which procedures are whitelisted for daytime | Planet DDS integrations | Blocking dependency |
| Client authorisation form per database | Customer | Required before credentials are issued |
| MNDA + Integration Agreement | PolyAI ↔ Planet DDS | Precedes any documentation or sandbox |
| Coverage-disclosure language | Customer + PolyAI | Must not imply adjudication |
Pick a flow, then click any node to see what the agent says, what it captures, or the exact Cloud 9 procedure it calls with per-field provenance. Purple nodes are shared sub-flows — click to drill in.
Each item below is captured by voice under adverse conditions and then used as a match key, a security verifier or a write payload. Strategy, validation and fallback for each.
Expand a use case to see the procedures it uses; expand a procedure for its parameters, its returns, and where each field comes from in the conversation. Nothing here is aspirational — every procedure listed is documented in the Planet DDS integration guide, and every field is traced to the moment in the call that produces it.
Three terms carry most of the meaning, and they are worth two minutes — they are the vocabulary this integration is scoped, approved and supported in.
Cloud 9 exposes its API as a set of named procedures rather than REST endpoints — the operation is named inside the request body, not in the address. Around a hundred exist across reads and writes; this guide uses twenty-one of them — seventeen live during the working day, four from the overnight sync.
Planet DDS grants standard API access between 00:00 and 11:00 UTC, and publishes a standing list of procedures that are additionally allowable during production hours — the practice's working day. Each carries a call interval in seconds, shown as its own badge.
Four small reference lists sit outside the daytime list — patient statuses, contact-info types and postal codes. PolyAI reads them inside the overnight window and caches them. They are vocabularies, not patient data.
NEW and a contact type described Home — are the pre-flight assertions that decide whether new-patient registration can work at all.| Badge | Meaning |
|---|---|
| Cloud 9 | The platform serving the call. Every procedure in these flows is Cloud 9's own — this integration introduces no third-party dependency. |
| GET SET | Planet DDS's own division of the API. GET reads; SET writes. There are around ninety GET procedures and exactly ten SET procedures — so the writes are worth knowing individually, and this guide uses seven of them. |
| GET · ref GET · sync | Reads that serve the overnight synchronisation rather than the live call — reference lists and incremental deltas respectively. GET · bulk denotes a full extract. |
| 1–3× | Expected calls per conversation, with conditional branches already weighted. This is what the Usage Estimator multiplies by your volumes. |
| Platform Fee | Planet DDS charges an initial and a monthly recurring integration fee. No per-call pricing is published, so call volume affects approval and rate limits rather than a per-transaction bill — which is why this guide estimates procedure volume rather than cost. |
| Verify | Behaviour the Planet DDS documentation does not settle. We have flagged it rather than assumed it, and it sits on the list to confirm with Planet DDS. |
| Gap | A capability confirmed absent from the documented API, with the workaround stated wherever one exists. |
Planet DDS publishes tiered bundle pricing with a generous included call allowance, so this estimates both procedure volume and cost — and shows how much of the included allowance the agent actually consumes.
Enter expected conversations per month for each use case.
Drives the Planet DDS pricing tier and the per-office call allowance.
Share of conversations reaching the end of the flow. Non-contained calls still consume the early lookups.
Each use case has a per-conversation call profile drawn from the flows in this guide. Conditional calls are weighted by how often that branch is expected to fire; fallback calls are weighted lower still. Volume is split into calls that run live during production hours and calls served from the overnight sync, so the two can be quoted separately.
Cost follows Planet DDS's 2026 API Pricing: the flows here need the Patient & Clinical and Appointments bundles, priced per office per month on a five-tier scale, with 130,000 calls included per office per bundle and additional calls at $0.0025. Cloud 9 counts as a single product, so the $10,000 implementation fee applies once.
SetAppointment, 15 for GetOnlineReservations) but does not state whether the interval is scoped per client database or per partner. Take the live figure and your expected peak concurrent calls per office to Planet DDS before committing to a launch volume.There is no OAuth flow, no token endpoint and no expiry. Every request carries the full credential set in its body — which changes how secrets, rotation and multi-tenancy have to be handled.
Planet DDS describe it as “basic authentication — username, password, and Client ID.” Note that despite the name, the credentials travel as elements in the XML body, not in an HTTP Authorization header.
| Element | Scope | Issued by |
|---|---|---|
ClientID | One GUID per client database | Planet DDS, after the client signs an authorisation form |
UserName | Static, per integration | Planet DDS |
Password | Static, per integration | Planet DDS |
VendorUserName | Per write — stamped as the actor on the record | PolyAI (max 25 chars) |
Cloud 9 models a group practice as separate databases, each with its own Client ID. Planet DDS sets the expectation directly: “The vendor or third-party must have the ability to update any number of URLs (databases) for each client.” PolyAI is built for exactly that shape.
(ClientID, GUID) rather than the GUID alone. It is a small design decision made once, and it is what lets a multi-location DSO run on one agent without records from two offices ever being confused for each other.The endpoint hostname is per-client too — us-ea1-partner is one region — and Planet DDS notes that calling the wrong endpoint for a client returns “BAD RESPONSE: HTTP/1.1 500 Server Error.” We treat the base URL as per-tenant configuration rather than a constant, so adding a location is a config change, not a code change.
| Testing (sandbox) | Production | |
|---|---|---|
| Endpoint | https://us-ea1-partnertest.cloud9ortho.com/GetData.ashx | https://us-ea1-partner.cloud9ortho.com/GetData.ashx |
| Availability | 24/7 open access, confirmed by Planet DDS | Overnight: full API · Daytime: allowable list |
| Scope | One endpoint, one sandbox, one ClientID | Per-client ClientID; multiple databases |
| Expiry | Inaccessible after 3 months idle; deactivated at 6 months (reactivation fee) | — |
Required before any documentation changes hands — for each individual party including subcontractors.
Required before entering a testing state, receiving a sandbox, or touching confidential data. Designates the integration type. Initial and monthly recurring fees attach here.
One endpoint, one ClientID. Vendor is expected to have a working integration within 6 months; an extension of 3–6 months needs written approval.
Planet DDS enables the write procedures against the Integration Agreement. SetAppointment and SetPatient are named as required, and must be live before testing — not just before production.
Vendor presents to Planet DDS before any production release. The service must be approved to proceed.
A small number of mutual clients who have authorised the service, in live production. Target duration ~1 month.
After beta sign-off the vendor may sell to any ortho Cloud 9 client. Each client database still needs its own signed authorisation form before credentials are issued.
Cloud 9's shape — XML bodies, credentials per call, a closed daytime window, no push events — means the integration layer does real work. This is where that work lives.
Each Cloud 9 procedure is wrapped in an Agent Studio function that owns the XML envelope, injects the credential triple from secrets, parses <Records> into typed output, and — importantly — maps ErrorCode to a branchable outcome rather than an exception.
| ErrorCode | Agent behaviour |
|---|---|
0 unknown error | Retry once, then handoff |
1 invalid credentials | Do not retry. Alert; handoff every call until resolved |
2/3/4/5 bad input | Re-capture the offending field, then retry once |
7 outside allowance window | Should not occur for the procedures used here — alert as config drift and fall back to cache for reads |
8 rate limited | The published interval was breached. Back off for the stated seconds; serve from cache where possible |
9/10 not allowable / not authorised | Configuration matter — alert and hand off. Never retry in a loop |
16 procedure validation failure | Surface the specific message; re-capture if it names a field |
200 OK, the wrapper must inspect <ResponseStatus> on every response. An HTTP-status-only check will treat every failure as a success and return an empty record set.| Item | Where |
|---|---|
ClientID, UserName, Password | Project secrets, per environment |
| Base endpoint URL | Config variable — per tenant, not global |
VendorUserName | Config variable (≤25 chars) |
Default locationGUID, providerGUID | Config page, per location variant |
| Schedule view / column GUID map | Refreshed by the nightly sync |
| Reason → appointment-type mapping | Config page, customer-owned |
GetLocations returns TimeZone per location — Cloud 9 expects you to know it, and returns datetime values in the plain 12/30/1999 12:00:00 AM format with no offset attached.
Variants re-scope: greeting and practice name, the location's timezone for all date maths, default provider and schedule view, and the bookable appointment-type subset.
A small scheduled job running inside the overnight window. Deliberately modest: patient records are read live and not retained, so this carries reference data only.
Patient statuses, contact-info types and postal codes — not on the daytime list, so they must be read in-window. Small, slow-changing, no patient data.
Not a sync task but the function it feeds: a Python step that normalises the captured surname and DOB, exact-matches DOB against PatientBirthDate across the candidates, and fuzzy-matches the name above a set threshold. Code, not an LLM judge — this decision gates PHI disclosure and must be deterministic and auditable. An LLM is used only to normalise ASR variants before the comparison.
Locations, appointment types, schedule views and columns, patient statuses, contact-info types, SMS carriers.
Assert a NEW patient status and a Home contact-info type exist. Surfacing these at deploy time turns what would be a quiet registration failure into a one-line config request.
Built alongside the sync: a per-procedure limiter honouring Planet DDS’s published call intervals so the agent never trips error 8.
Everything here was checked against the full procedure index of the 12.10 C9 release guide. Nothing on this page is an assumption of absence — each is a confirmed absence, or a question the document leaves genuinely open.
These are supported by the API but excluded from the flows in this guide.
| Capability | Procedure | Why excluded |
|---|---|---|
| Financial balances & aging | GetResponsiblePartyBalance, GetAgingReportData | Reading a balance is feasible, but no payment can be taken — quoting a balance with no way to settle it creates a worse call, not a better one. |
| Contract / payment plan detail | GetContracts, GetInsuranceContracts | Same reason. Belongs to a treatment-coordinator conversation, not an automated one. |
| Recall & continuing care | GetRecallListByDate, GetPatientsContinuingCare | Genuinely useful for outbound campaigns — out of scope for the five inbound use cases here. |
| No-show & dismissed lists | GetNoShowList, GetLastDismissedAppointments | Outbound reactivation, not inbound handling. |
| “Sooner if possible” waitlist | GetSoonerIfPossibleAppointments | Read-only. The flag can be read but not set, so the agent cannot add a caller to the waitlist. |