DodoDentist for developers

Connect assistants to PHI-free clinic operations through MCP, or wire your own tools into the scoped DodoDentist REST API and signed webhooks.

Model Context Protocol

Safe clinic operations in Claude and ChatGPT

Connect DodoDentist to Claude, ChatGPT, or another MCP client at https://mcp.dododentist.com/mcp. OAuth asks for the read-only dododentist:read scope and keeps every result inside the organization you authorize.

{
  "mcpServers": {
    "dododentist": {
      "type": "http",
      "url": "https://mcp.dododentist.com/mcp"
    }
  }
}

Administrative tools only

The connector can list safe clinic metadata, bookable availability, provider display names, procedure-catalog names and prices, non-identifying calendar blocks, and aggregate schedule counts by day. Its interactive views summarize the same bounded operational data.

The public MCP never returns patient records or contact details, individual appointment records, diagnoses, treatment information, clinical notes, anamnesis, tooth charts, or other health data. It cannot create or change records and does not make clinical or medical decisions.

Use the REST API below only when your own reviewed integration genuinely needs record-level access. REST API keys and their data-handling obligations are separate from the narrower MCP connector.

CLI

Command-line interface

The same clinics, patients, appointments, treatments, invoices and payments are available from your terminal through the dododentist CLI. Install it globally with npm, or run it ad hoc with npx.

There is no account step to do elsewhere: dododentist signup creates one from the terminal, and dododentist login signs in to an existing account either through the browser or with an API key secret. For scripts and CI, export DODODENTIST_API_KEY instead and skip the login step entirely.

# Install once, globally
npm install -g dododentist
# or run it ad hoc without installing
npx dododentist --help

# Create an account without leaving the terminal
dododentist signup --email dentist@example.com

# Or log in to an existing one — browser, or an API key secret
dododentist login

# The clinics this credential can reach
dododentist clinics list

# This week's schedule for one of them
dododentist appointments list --clinicId CLINIC_ID \
  --startTime 2026-09-01T00:00:00.000Z --endTime 2026-09-08T00:00:00.000Z

# Book a visit
dododentist appointments add --clinicId CLINIC_ID --patientId PATIENT_ID \
  --startTime 2026-09-01T09:00:00.000Z --endTime 2026-09-01T09:30:00.000Z

Every data command accepts --json for parseable output, errors go to stderr with exit code 1, and dododentist schema prints the whole command tree as JSON so a script never has to scrape help text.

The CLI is open source at github.com/dododentist/cli and published as dododentist on npm. Run any command with --help to see its options.

Agent Skills

Teach your coding agent DodoDentist

DodoDentist ships Agent Skills — guides following the agentskills.io standard that teach coding agents how to work with a dental practice through the dododentist CLI and the MCP connector, instead of guessing at commands and tools.

# Install the DodoDentist skills into your coding agent
npx skills add dododentist/skills

One command installs the skills into Claude Code, Cursor, Codex, Gemini CLI and any other agent that follows the Skills standard. The CLI also bundles the same guides, version-matched to the commands it ships: dododentist skills get <name> prints one on demand.

The two skills mirror the two surfaces and are deliberately not equivalent. The CLI skill covers the record-level REST API and states the duty of care that comes with patient data; the clinic-administration skill covers the MCP connector, which is administrative-only and cannot reach patient or clinical data at all.

The skills are open source at github.com/dododentist/skills. Claude users can also install the DodoDentist Claude plugin, which bundles the connector together with its skill: github.com/dododentist/claude-plugin.

API keys

Get a key and authenticate

The DodoDentist REST API is for a practice that wants to run its own tooling on top of its records: migrate patients in from another practice-management system, keep an in-house booking page or reporting sheet in step with the calendar, or push invoices and payments into your accounting stack.

A key belongs to your organization and can only be created by an organization administrator. The secret is shown when the key is created; afterwards an administrator can reveal it again from the dashboard by re-entering their password. Because the key carries its own tenant, the organization is implied by the key and never has to be sent.

Authenticate every request with HTTP Basic auth carrying only the key secret, base64-encoded, in the Authorization header.

# The Authorization header is HTTP Basic auth carrying only the key secret,
# with no username and no colon.
Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)

Every endpoint lives under https://api.dododentist.com. Requests made with a key are rate limited per key; going over the limit returns 429.

Quick start

Your first three calls

Find your clinic, list its patients, then book an appointment for one of them.

# List the clinics your key can reach
curl https://api.dododentist.com/api/clinics \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

# List the patients of one of those clinics
curl "https://api.dododentist.com/api/patients?clinicId=CLINIC_ID" \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

# Book an appointment for one of them.
# Times are ISO 8601 instants.
curl -X POST https://api.dododentist.com/api/appointments \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "clinicId": "CLINIC_ID",
    "patientId": "PATIENT_ID",
    "startTime": "2026-09-01T09:00:00.000Z",
    "endTime": "2026-09-01T09:30:00.000Z"
  }'

Browse the full API reference — every endpoint with its parameters, request body, responses and required scope.

Scopes

Least privilege by default

Each key carries a list of scopes, so a reporting script that only needs to read your calendar never gets the ability to change a patient record. New keys start read-only; widen them explicitly. A request whose key is missing the scope an endpoint requires is refused with 403.

  • patients:readList the patients of a clinic and read a single patient.
  • patients:writeCreate, update and delete patients.
  • appointments:readList appointments and read a single appointment.
  • appointments:writeCreate, update and delete appointments.
  • clinics:readList the clinics of your organization.
  • treatments:readRead the treatments catalogue of a clinic.
  • invoices:readRead invoices.
  • invoices:writeCreate, update and delete invoices.
  • payments:readRead payments.
  • payments:writeCreate, update and delete payments.

Patient records are health data. A key is a credential over that data, so give each integration its own key with the narrowest scopes it can work with, and delete a key the moment the integration is retired.

Webhooks

Signed webhooks

Add a webhook subscription to your clinic and DodoDentist POSTs the events you picked to your server as they happen, so your own systems do not have to poll.

  • patient.createdA patient was created.
  • patient.updatedA patient record changed.
  • patient.deletedA patient was deleted.
  • appointment.createdAn appointment was booked.
  • appointment.updatedAn appointment was moved or its status changed.
  • appointment.deletedAn appointment was deleted.
  • invoice.createdAn invoice was issued.
  • invoice.updatedAn invoice was edited.
  • invoice.deletedAn invoice was deleted.
  • payment.createdA payment was recorded.
  • payment.updatedA payment was edited.
  • payment.deletedA payment was deleted.
POST https://your-server.com/dododentist-webhook
X-Dododentist-Event: appointment.created
X-Dododentist-Signature: t=1719000000,v1=<hmac-sha256 hex>
Content-Type: application/json

{
  "event": "appointment.created",
  "timestamp": 1719000000,
  "data": { "...": "..." }
}

Verify the signature

Every delivery carries an X-Dododentist-Signature header of the form t=timestamp,v1=signature, where the signature is an HMAC-SHA256 of timestamp.body keyed by the subscription secret shown to you once when the subscription was created. Recompute it over the raw body and compare before trusting the payload.

import crypto from 'node:crypto'

// body must be the RAW request body, byte for byte
function verify(header, body, secret) {
  const [t, v1] = (header || '').split(',').map(part => part.split('=')[1])
  if (!t || !v1) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${body}`)
    .digest('hex')

  // timingSafeEqual throws on a length mismatch, so a malformed signature
  // has to be rejected before the comparison rather than by it.
  if (v1.length !== expected.length) return false

  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Delivery is one best-effort attempt with a five second timeout and no retries, so respond 2xx quickly and do the work asynchronously. An endpoint that fails twenty times in a row is disabled automatically and has to be re-enabled.

Start building