A field reference for PolyAI voice agents integrating with Planet DDS Cloud 9 Ortho. Built strictly from Planet DDS's Basic Guide and Commonly Used APIs (12.10 C9 Release, October 2025). 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. It is a single ASP.NET handler that dispatches on a <Procedure> element inside an XML body. Understanding three properties of that design determines whether a voice integration is viable at all.
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.
Production access is granted 00:00–11:00 UTC only. Calls outside that window fail with error 7 unless the specific procedure has been whitelisted by Planet DDS for daytime use. This is the single largest design constraint.
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.
GetScheduleViewColumnList Join — verifySetAppointment requires a ScheduleColumnGUID. GetOnlineReservations returns the column only as a description string. This resolution step is a documented gap — see Gaps.
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 · Setup-gated |
| Book | SetAppointment | Column-GUID join unresolved |
| 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 — | Requires clearinghouse |
| 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.
Page 4 of the Planet DDS integration guide: “All authorized vendors or third parties will have access to Cloud 9 APIs for 7 days a week between the hours of 12:00:00 AM – 11:00:00 AM UTC.” Everything else in this guide is downstream of that sentence.
The window is fixed in UTC and does not shift for daylight saving — so it drifts an hour against US local time twice a year. The vendor guide calls this out explicitly.
| Zone | API open | Overlap with 8am–5pm local |
|---|---|---|
| Eastern · EST (UTC−5) | 7:00 PM – 6:00 AM | None |
| Eastern · EDT (UTC−4) | 8:00 PM – 7:00 AM | None |
| Central · CDT (UTC−5) | 7:00 PM – 6:00 AM | None |
| Mountain · MDT (UTC−6) | 6:00 PM – 5:00 AM | None |
| Pacific · PST (UTC−8) | 4:00 PM – 3:00 AM | None |
| Pacific · PDT (UTC−7) | 5:00 PM – 4:00 AM | None |
Three of the fourteen documented error codes exist solely to police this.
| Code | Message |
|---|---|
7 | Not authorized to collect data outside of allowance window. |
9 | Procedure < > not whitelisted for partner / client. The current allowance window for non-whitelisted procedures is < > - < >. |
8 | Too many requests within the timeframe for procedure < >. |
Code 9 is the important one: it proves a per-procedure whitelist exists and that whitelisted procedures get a different allowance window. Code 8 confirms per-procedure rate limiting, whose thresholds are not published.
Neither is included in the guide we hold. Both are named on pages 4–5 as available on request from cloud9.integrations@planetdds.com.
The authoritative list of which procedures may run in daytime. Nothing in this guide can be committed to a customer until this list is in hand.
Page 5: “If a vendor or third-party plans to integrate a new patient online scheduling service, please reach out to the Cloud 9 integrations team for a recommended process workflow.” Directly governs the New Patient Intake flow.
Also referenced and worth requesting: Converting Person with SetPatient, Document Uploads, Insurance Claim APIs.
The design that survives both outcomes: pursue whitelisting for the small set of write procedures that genuinely must happen live, and serve every read from a locally-synced cache so identification and slot presentation never depend on the window at all.
| Procedure | Purpose |
|---|---|
GetPatientList ModifiedSince | Patient demographics delta |
GetPatientPhone | Builds the reverse-phone index |
GetAppointmentListSince dtSince | Appointment delta, paged |
GetLocations | Location + per-location timezone |
GetAppointmentTypes | Type list + AllowOnlineScheduling |
GetScheduleViewColumnList | View/column GUID map for the join |
GetPatientStatuses · GetContactInfoTypes | Precondition checks for SetPatient |
| Procedure | Why it cannot be cached |
|---|---|
GetOnlineReservations | Slot availability is stale within minutes; double-booking risk |
SetAppointment | A write. Deferring it means the caller leaves without a booking |
SetAppointmentStatusCanceled | A write, and the release must free the slot for others |
SetAppointmentStatusConfirmed | A write |
SetPatient | A write; blocks the booking that follows it |
GetPatientAppointments | Cacheable, but staleness risks acting on an already-cancelled appointment |
The integration does not become impossible, but it changes shape. Be explicit with the customer about which of these they are buying.
| Capability | Cache-only behaviour | Acceptable? |
|---|---|---|
| Identify the caller | Full fidelity from the nightly index, including reverse-phone | Yes |
| Read back an upcoming appointment | Accurate as of last sync; risks quoting a slot changed same-day | Qualified |
| Confirm an appointment | Queued and flushed in the next window — the caller is told “confirmed,” the chart updates overnight | Qualified |
| Cancel | Queued; the slot is not released until overnight, so it cannot be re-offered same-day | Qualified |
| Book a specific slot | Cannot be honoured — availability is unverifiable and the write cannot land | No |
| New patient registration | Cannot create the record during the call | No |
SetAppointment is not whitelisted, the agent captures the request and hands off warmly — it will not tell a caller they hold a slot the system has not accepted. Trust in the agent is built on exactly this kind of restraint.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.
Planet DDS grants partner API access between 00:00 and 11:00 UTC as standard. Procedures approved to run outside that window — during the practice's working day — are whitelisted individually by the Planet DDS integrations team, against a documented business case.
Everything that does not need to be live. PolyAI keeps a synchronised copy of reference and patient data, refreshed overnight inside the standard access window, using Cloud 9's own incremental delta procedures.
| 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 vendor 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. |
Cloud 9 does not publish per-transaction API pricing — the guide states only that “there are initial and monthly recurring integration fees.” So this estimates procedure volume, which is what the whitelist and rate-limit conversations with Planet DDS actually turn on.
Enter expected conversations per month for each use case.
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 whitelist-required (must run live, daytime) and cacheable (can be served from the nightly sync) so the two numbers can be quoted separately.
8 — “Too many requests within the timeframe for procedure” — confirms per-procedure rate limits exist, but no thresholds are published. Take the whitelist-required figure to Planet DDS and ask them to confirm it clears their limits before committing to a 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.
| 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) |
The guide is explicit: “The vendor or third-party must have the ability to update any number of URLs (databases) for each client. Each database will have a unique Client ID (GUID).”
(ClientID, GUID) — never on the GUID alone. A DSO with per-location databases will collide otherwise.The endpoint hostname is also per-client: us-ea1-partner is one region. The guide notes that calling the wrong endpoint for a client returns “BAD RESPONSE: HTTP/1.1 500 Server Error.” Treat the base URL as per-tenant configuration, not a constant.
| Testing (sandbox) | Production | |
|---|---|---|
| Endpoint | https://us-ea1-partnertest.cloud9ortho.com/GetData.ashx | https://us-ea1-partner.cloud9ortho.com/GetData.ashx |
| Availability | 24/7, minus maintenance windows | 00:00–11:00 UTC, 7 days |
| Scope | One endpoint, one sandbox, one ClientID | Per-client ClientID; multiple databases |
| Expiry | Inaccessible after 3 months idle; deactivated at 6 months (reactivation fee) | — |
7 in production at 10am Eastern — so the whitelist conversation should start well before testing finishes.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.
Business case + minimum data needs for daytime procedures. Do this early — it gates the architecture, not just the launch.
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 | Fall back to cache for reads; for writes, capture and handoff |
8 rate limited | Back off; serve from cache if the procedure is cacheable |
9/10 not whitelisted / not authorised | Configuration fault — alert and handoff. 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 scheduled job running inside the open window. It is part of the integration, not infrastructure trivia — several flows are unbuildable without it.
GetPatientList(ModifiedSince) and GetAppointmentListSince(dtSince) — both paged, pageSize capped at 1000.
GetPatientPhone and GetResponsiblePartyPhone return every number with no phone-side filter. Normalise to E.164 and index locally — this is the only route to caller-ID identification.
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. Fail loudly — these silently break new-patient registration.
Only if writes are not whitelisted. Flush confirmations and cancellations in the next window, and reconcile against the following delta.
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. |