The Data Co API (v1)

Base URL

https://api.thedataco.com/v1

Authentication

Send your API key in the Authorization header:

Authorization: Bearer tdc_live_...

Keys are scoped to one organization and one environment. Sandbox keys start with tdc_test_ and return demo data; live keys start with tdc_live_.

Keep keys in a secret manager or backend environment variable. Do not use them in browser, mobile, or desktop clients, and do not commit them to source control.

Quickstart

# check your key, scopes, and connections
curl https://api.thedataco.com/v1/access \
  -H "Authorization: Bearer tdc_live_..."

# first page of revenue entries
curl "https://api.thedataco.com/v1/revenue-entries?limit=500" \
  -H "Authorization: Bearer tdc_live_..."

# next page
curl "https://api.thedataco.com/v1/revenue-entries?limit=500&cursor=..." \
  -H "Authorization: Bearer tdc_live_..."

Response Format

List endpoints:

{
  "data": [],
  "pagination": {
    "nextCursor": null,
    "hasMore": false,
    "limit": 500
  }
}

Single-record endpoints:

{
  "data": {}
}

Every response carries an X-Request-Id header. Include it when contacting support about a request.

Pagination

Parameter Description
limit Records per page. Default 500, maximum 5000.
cursor The pagination.nextCursor of the previous page.

Records are ordered by updatedAt ascending, then id. Request the next page with nextCursor until hasMore is false.

Cursors are opaque and valid for at least 24 hours. A cursor is bound to the key, the resource, and the updatedSince of the request that issued it; limit may change between pages. An invalid, expired, or mismatched cursor returns 400 invalid_cursor: restart from your last updatedSince checkpoint.

Incremental Sync

Every list endpoint accepts updatedSince, an inclusive lower bound on updatedAt as an ISO 8601 UTC timestamp.

Initial sync: request each resource without updatedSince, page to the end, upsert by id and connectionId, and store the maximum updatedAt seen.

Ongoing sync: request each resource with updatedSince set to your stored checkpoint, page to the end, upsert, and store the new maximum updatedAt. Treat a record with a non-null deletedAt as deleted; keep the row so references still resolve.

updatedAt changes only when a record's values change, including when it is deleted. Because updatedSince is inclusive, a record can appear in two consecutive syncs; upserts make this safe. A reference to a record you have not synced yet resolves on the next sync.

Sync Status

GET /v1/sync-status returns, for each resource your key can read, when it last finished publishing.

{
  "data": [
    { "resource": "appointments", "latestSyncAt": "2026-05-12T06:15:00Z" },
    { "resource": "revenue-entries", "latestSyncAt": "2026-05-12T05:58:12Z" },
    { "resource": "campaigns", "latestSyncAt": null }
  ]
}

latestSyncAt is per resource and is null for a resource that has never published. Checkpoint incremental syncs on updatedAt, not on this value.

Identifiers

Resource id Notes
banners, clinics integer Organization-level.
employees, patients, appointments, revenue-entries, payments, leads string Opaque. Identified with connectionId.
campaigns string Opaque. One campaign on one day; campaignId groups a campaign across days.

A connection is one source system connected to your organization: an EMR, a CRM, an advertising account. Every record on the seven connection-scoped resources carries the opaque connectionId of the connection it came from, and a record is identified by (id, connectionId). GET /v1/access lists your connections.

Store ids as strings of unbounded length. Do not parse them or sort on them.

Data Types

Type Format
Date YYYY-MM-DD
Timestamp ISO 8601 UTC, e.g. 2026-05-12T14:30:00Z
Clinic-local time YYYY-MM-DDTHH:MM:SS with no zone, e.g. 2026-05-12T14:00:00, in the clinic's timezone. Used for appointment startTime, endTime, and createdAt.
Decimal and money JSON string, e.g. "1250.00". Money is USD.
Integer JSON number
Boolean JSON boolean
Empty value null

Enumerated Values

Values are case-sensitive. The sets may grow.

gender (patients)

Male, Female, Not Specified

status (appointments)

scheduled, confirmed, cancelled, rescheduled, no_show, completed, in_progress, unknown

role (employees)

Unassigned, Aesthetician, Aesthetician - All Devices, Consultant, Dermatologist,
Doctor, Hybrid Injector, Injector, Laser Technician, Nurse, Nurse Injector,
Support Staff, Surgeon, Therapist, Wellness

parentCategory (appointments, revenue-entries)

Surgical Procedures, Products, Injectables, Energy and Device Treatments,
Body Contouring (Non-Surgical), Clinical Aesthetics, Wellness and Hormones,
Hair Restoration, Consults & Follow Ups, Medical and General, Admin, Fees

subCategory and serviceCategory (appointments, revenue-entries)

parentCategory subCategory serviceCategory values
Surgical Procedures Face Facelift, Neck Lift, Lip Lift, Cheek Augmentation, Cheek Reduction, Buccal Fat Removal, Blepharoplasty, Rhinoplasty, Brow Lift, Otoplasty, Chin Augmentation, Scar Revision, Earlobe Repair, Fat Grafting, Other
Surgical Procedures Breast Breast Augmentation, Breast Lift (Mastopexy), Breast Reduction, Implant Removal/Exchange, Scar Revision, Fat Grafting, Nipple Repair, Other
Surgical Procedures Body Arm Lift, Butt Lift, Liposuction, Mommy Makeover, Thigh Lift, Tummy Tuck, Labiaplasty, Scar Revision, Fat Grafting, Other
Surgical Procedures Hair FUE, FUT, Neograft, Artas, Other
Surgical Procedures Reconstructive Breast Reconstruction, Skin Cancer Reconstruction, Trauma Repair, Other
Surgical Procedures Gender-Affirming Top Surgery, Facial Feminization, Facial Masculinization, Body Contouring (Gender-Affirming), Tracheal Shave, Other
Surgical Procedures Other Scar Revision, Other
Surgical Procedures Adjustments Adjustments
Products Surgical Implants Breast Implants, Allografts, Other
Products Surgical Garments Compression Garments, Surgical Bras, Abdominal Binders, Other
Products Other Surgical Supplies Puregraft, Other
Products Skincare Sunscreen, Cleanser, Moisturizer, Serum, Eye Cream, Mask, Other
Products Prescription Latisse, Upneeq, Hydroquinone, Other
Products Other Supplements, Other
Injectables Neurotoxins Botox, Dysport, Xeomin, Jeuveau, Daxxify, Other
Injectables Fillers Juvederm, Restylane, RHA, Revanesse, Belotero, Evolysse, PhalloFILL, Other
Injectables Biostimulators Sculptra, Radiesse, Bellafill, Renuva, alloClae, ariessence, Other
Injectables Regenerative PRP, PRF, Exosomes, EZGEL, Other
Injectables Threads PDO Threads, InstaLift, Other
Injectables Dissolvers Hyaluronidase, Other
Injectables Skin Quality Skinvive, Skinboosters, Mesotherapy, Other
Injectables Vein Therapy Sclerotherapy, Asclera, Other
Injectables Other Other
Energy and Device Treatments Laser Hair Removal Laser Hair Removal, Electrolysis, Other
Energy and Device Treatments Laser Resurfacing (Ablative) CO2, Erbium, UltraClear, Other
Energy and Device Treatments Laser Resurfacing (Non-Ablative) Halo, Fraxel, Moxi, Clear + Brilliant, Icon 1540, ResurFX, AgeJET, Other
Energy and Device Treatments Skin Resurfacing Microneedling, AquaGold, Dermaplaning, Microdermabrasion, HydraFacial, DiamondGlow, Other
Energy and Device Treatments Pigment and Vascular IPL, BBL, Vbeam, Nd:YAG, Other
Energy and Device Treatments Light Therapy Red Light, PDT, Blue Light, Other
Energy and Device Treatments RF Microneedling Morpheus8, Sylfirm X, Potenza, Vivace, Genius, Exion, Other
Energy and Device Treatments Skin Tightening Aerolase, Ultherapy, Sofwave, Thermage, Exilis, Pelleve, ThermiSmooth, SkinTyte, FaceTite, AccuTite, Other
Energy and Device Treatments Sweat Reduction MiraDry, Other
Energy and Device Treatments Acne Therapy AviClear, Other
Energy and Device Treatments Tattoo Removal PicoSure, Picoway, Q-Switch, Other
Energy and Device Treatments Other Other
Body Contouring (Non-Surgical) Fat Reduction CoolSculpting, SculpSure, Vanquish, Kybella, Other
Body Contouring (Non-Surgical) Muscle Stimulation Emsculpt, CoolTone, TruSculpt Flex, Other
Body Contouring (Non-Surgical) Cellulite Reduction BodyTite, Venus Legacy, Emtone, Qwo, Other
Body Contouring (Non-Surgical) Other Other
Clinical Aesthetics Facials Oxygen, Acne Facial, Extractions, Other
Clinical Aesthetics Chemical Peels PCA, VI, BioRePeel, TCA, Glycolic, Jessner, Other
Clinical Aesthetics Hair Removal (Non-Laser) Waxing, Sugaring, Threading, Other
Clinical Aesthetics Lashes, Brows, and Permanent Makeup Microblading, Lip Blushing, Lamination, Tints, Permanent Makeup, Other
Clinical Aesthetics Other Piercing, Spray Tanning, Other
Wellness and Hormones Medical Weight Loss Semaglutide, Tirzepatide, Phentermine, Other
Wellness and Hormones Hormone Therapy BioTE Pellets, Testosterone, HRT Management, Other
Wellness and Hormones IV and Vitamin Therapy Myers Cocktail, NAD+, B12/MIC Shots, Other
Wellness and Hormones Sexual Wellness O-Shot, P-Shot, Emsella, Empower, MonaLisa Touch, ThermiVa, Diva, Other
Wellness and Hormones Spa Chiropractic, Acupuncture, Massage, Scrubs, Other
Wellness and Hormones Other Peptides, Other
Hair Restoration Restoration PRP, PRF, KeraLase, TED, Other
Hair Restoration Other Other
Consults & Follow Ups Consults Aesthetic Consult, Surgical Consult, Other
Consults & Follow Ups Follow Ups Aesthetic Follow Up, Surgical Follow Up, Other
Medical and General Dermatology Mole Removal, Skin Tags, Cyst Excision, Kenalog, Other
Medical and General Add-Ons Numbing, Pronox, Nitrous Oxide, Other
Medical and General Diagnostics Biopsies, Pathology, Labs, Genetic Testing, Other
Medical and General Conditions Acne Consults, Rosacea, Eczema, Hyperhidrosis Consults, Other
Medical and General Other Other
Admin Admin Fees Booking Deposits, Cancellation Fees, No Show Fees, Other
Admin Non-Revenue Gift Card, Package, Membership, Tips, Donations, Other
Admin Misc Shipping, Taxes, Training, Events, Other
Admin Other Other
Fees Anaesthesia Anaesthesia, Other
Fees OR Fees OR Fees, Facility Fees, Other

channel (leads)

3rd Party, Email, Entity Med, Events, Google, Meta, Organic Search, Other,
Phone, Referral, Returning Patient, Social Media, Walk-in, Website

revenueCategory (appointments, revenue-entries) is MedSpa or Surgery.

channel on campaigns, revenueCategory on campaigns and leads, status, paymentMethod, and paymentCategory on payments, and region on clinics are free-form strings.

Permissions

Each key holds read scopes:

Scope Endpoint
banners:read /banners
clinics:read /clinics
employees:read /employees
patients:read /patients
appointments:read /appointments
revenue_entries:read /revenue-entries
payments:read /payments
campaigns:read /campaigns
leads:read /leads

A key may be restricted to clinics or banners. A restricted key sees appointments, revenue entries, and payments for its clinics (a granted banner includes all of its clinics), banners containing at least one of those clinics, and the campaigns and leads of those banners. Employees and patients are organization-wide; for a restricted key, patient lifetimeRevenue and firstRevenueDate are null.

Endpoints

Every list endpoint accepts updatedSince, limit, and cursor. /banners and /clinics also accept id and bannerId. Any other query parameter returns 400 invalid_request.

Every resource serves one record at GET /v1/{resource}/{id}. The seven connection-scoped resources also accept ?connectionId= on that lookup; if an id matches records from more than one connection, the lookup returns 400 invalid_request with the candidate connectionIds in error.details.

GET /access

Returns the key's context. Requires no scope.

Field Type Notes
keyId string Key identifier.
name string Key display name.
environment string live or test.
organization.id string
organization.name string
scopes string[] Granted read scopes.
clinicIds integer[] Clinic restrictions. Empty with bannerIds means unrestricted.
bannerIds integer[] Banner restrictions.
connections[].connectionId string The connectionId carried by that connection's records.
connections[].platform string Source system, e.g. zenoti.
connections[].label string Name given to the connection. null if none.
connections[].status string active, pulling, pending, requested, or disabled. A disabled connection's records are not served.
rateLimit.requestsPerMinute integer
rateLimit.requestsPerDay integer
expiresAt timestamp null if the key does not expire.
{
  "data": {
    "keyId": "ak_123",
    "name": "Customer warehouse sync",
    "environment": "live",
    "organization": { "id": "example-clinics", "name": "Example Clinics" },
    "scopes": ["appointments:read", "revenue_entries:read"],
    "clinicIds": [12, 18],
    "bannerIds": [],
    "connections": [
      { "connectionId": "int_9f8c2b415d3e4a17", "platform": "zenoti", "label": "Austin clinics", "status": "active" },
      { "connectionId": "int_4a1e7c0b8d62f350", "platform": "gohighlevel", "label": null, "status": "active" }
    ],
    "rateLimit": { "requestsPerMinute": 120, "requestsPerDay": 50000 },
    "expiresAt": null
  }
}

GET /sync-status

Returns resource and latestSyncAt for each resource the key can read. See Sync Status.

GET /banners

Scope: banners:read

Parameter Description
id One or more banner ids, repeated. Maximum 100.
updatedSince Inclusive lower bound on updatedAt.
Field Type
id integer
name string
createdAt timestamp
updatedAt timestamp
{
  "id": 3,
  "name": "Austin MedSpa Group",
  "createdAt": "2024-01-08T00:00:00Z",
  "updatedAt": "2026-05-02T17:41:09Z"
}

GET /clinics

Scope: clinics:read

Parameter Description
id One or more clinic ids, repeated. Maximum 100.
bannerId One or more banner ids, repeated.
updatedSince Inclusive lower bound on updatedAt.
Field Type Notes
id integer
name string
bannerId integer
address.line1 string
address.city string
address.state string
address.postalCode string
address.country string
region string
currency string
timezone string IANA name, e.g. America/Chicago. The zone of the clinic's appointment times.
createdAt timestamp
updatedAt timestamp
{
  "id": 12,
  "name": "Main Street Clinic",
  "bannerId": 3,
  "address": {
    "line1": "123 Main Street",
    "city": "Austin",
    "state": "TX",
    "postalCode": "78701",
    "country": "US"
  },
  "region": "Southwest",
  "currency": "USD",
  "timezone": "America/Chicago",
  "createdAt": "2024-01-08T00:00:00Z",
  "updatedAt": "2026-05-02T17:41:09Z"
}

GET /employees

Scope: employees:read

Field Type Notes
id string
connectionId string
fullName string
active boolean
role string Enumerated. null if unassigned.
firstActivityDate date
createdAt timestamp null.
updatedAt timestamp
deletedAt timestamp Set when deleted at the source.
{
  "id": "4821",
  "connectionId": "int_9f8c2b415d3e4a17",
  "fullName": "Jane Smith",
  "active": true,
  "role": "Nurse Injector",
  "firstActivityDate": "2024-03-12",
  "createdAt": null,
  "updatedAt": "2026-05-12T14:30:00Z",
  "deletedAt": null
}

GET /patients

Scope: patients:read

Field Type Notes
id string
connectionId string
firstName string
lastName string
fullName string
gender string Enumerated.
dateOfBirth date
email string
homePhone string As recorded at the source.
mobilePhone string As recorded at the source.
firstActivityDate date
firstRevenueDate date null for restricted keys.
lifetimeRevenue money null for restricted keys.
createdAt timestamp null.
updatedAt timestamp
deletedAt timestamp Set when deleted at the source.
{
  "id": "7734",
  "connectionId": "int_9f8c2b415d3e4a17",
  "firstName": "Dana",
  "lastName": "Whitfield",
  "fullName": "Dana Whitfield",
  "gender": "Female",
  "dateOfBirth": "1985-07-02",
  "email": "dana.whitfield@example.com",
  "homePhone": null,
  "mobilePhone": "5551234567",
  "firstActivityDate": "2024-03-12",
  "firstRevenueDate": "2024-03-20",
  "lifetimeRevenue": "1250.00",
  "createdAt": null,
  "updatedAt": "2026-05-12T14:30:00Z",
  "deletedAt": null
}

GET /appointments

Scope: appointments:read

Field Type Notes
id string
connectionId string
date date Clinic-local business date.
clinicId integer
employeeId string null if unassigned.
patientId string null if unassigned.
startTime clinic-local time
endTime clinic-local time
durationHours decimal
status string Enumerated.
parentCategory string Enumerated.
subCategory string Enumerated.
serviceCategory string Enumerated.
revenueCategory string MedSpa or Surgery.
createdAt clinic-local time When booked.
updatedAt timestamp
deletedAt timestamp Set when deleted at the source.
{
  "id": "90114",
  "connectionId": "int_9f8c2b415d3e4a17",
  "date": "2026-05-12",
  "clinicId": 12,
  "employeeId": "4821",
  "patientId": "7734",
  "startTime": "2026-05-12T14:00:00",
  "endTime": "2026-05-12T15:00:00",
  "durationHours": "1.00",
  "status": "completed",
  "parentCategory": "Injectables",
  "subCategory": "Neurotoxins",
  "serviceCategory": "Botox",
  "revenueCategory": "MedSpa",
  "createdAt": "2026-04-28T09:12:00",
  "updatedAt": "2026-05-12T14:30:00Z",
  "deletedAt": null
}

GET /revenue-entries

Scope: revenue_entries:read

Field Type Notes
id string
connectionId string
date date Business date.
clinicId integer
employeeId string null if unassigned.
patientId string null if unassigned.
invoiceId string Groups the entries of one invoice.
revenue money
total money
quantity decimal
discount money
tax money
parentCategory string Enumerated.
subCategory string Enumerated.
serviceCategory string Enumerated.
revenueCategory string MedSpa or Surgery.
createdAt timestamp null.
updatedAt timestamp
deletedAt timestamp Set when deleted at the source.
{
  "id": "552310",
  "connectionId": "int_9f8c2b415d3e4a17",
  "date": "2026-05-12",
  "clinicId": 12,
  "employeeId": "4821",
  "patientId": "7734",
  "invoiceId": "INV-88213",
  "revenue": "950.00",
  "total": "1000.00",
  "quantity": "1.00",
  "discount": "50.00",
  "tax": "0.00",
  "parentCategory": "Injectables",
  "subCategory": "Neurotoxins",
  "serviceCategory": "Botox",
  "revenueCategory": "MedSpa",
  "createdAt": null,
  "updatedAt": "2026-05-12T14:30:00Z",
  "deletedAt": null
}

GET /payments

Scope: payments:read

Field Type Notes
id string
connectionId string
status string
total money
paymentDate date Business date.
effectiveDate date Accounting date.
paymentMethod string
paymentCategory string
clinicId integer
patientId string
createdAt timestamp
updatedAt timestamp
deletedAt timestamp Set when deleted or voided at the source.
{
  "id": "771204",
  "connectionId": "int_9f8c2b415d3e4a17",
  "status": "paid",
  "total": "1000.00",
  "paymentDate": "2026-05-12",
  "effectiveDate": "2026-05-12",
  "paymentMethod": "Credit Card",
  "paymentCategory": "Patient Payment",
  "clinicId": 12,
  "patientId": "7734",
  "createdAt": "2026-05-12T14:05:00Z",
  "updatedAt": "2026-05-12T14:30:00Z",
  "deletedAt": null
}

GET /campaigns

Scope: campaigns:read

One row per campaign per day. impressions, clicks, and spend are that day's totals.

Field Type Notes
id string One campaign on one day.
connectionId string
channel string
campaignId string The same across a campaign's days.
campaignName string
date date The day the metrics cover.
startTime timestamp Campaign start.
endTime timestamp Campaign end.
impressions integer
clicks integer
spend money
bannerId integer
revenueCategory string
createdAt timestamp null.
updatedAt timestamp
{
  "id": "98431:2026-05-12",
  "connectionId": "int_9f8c2b415d3e4a17",
  "channel": "Meta",
  "campaignId": "98431",
  "campaignName": "Spring Botox Promo",
  "date": "2026-05-12",
  "startTime": "2026-04-01T07:00:00Z",
  "endTime": "2026-06-30T07:00:00Z",
  "impressions": 18420,
  "clicks": 322,
  "spend": "415.77",
  "bannerId": 33,
  "revenueCategory": "MedSpa",
  "createdAt": null,
  "updatedAt": "2026-05-13T06:15:00Z"
}

GET /leads

Scope: leads:read

Field Type Notes
id string
connectionId string
contactId string CRM contact.
patientId string null until matched to a patient.
patientConnectionId string Connection of the matched patient. null when patientId is null.
employeeId string null if unassigned.
bannerId integer
channel string Enumerated.
campaignId string Joins to campaignId on /campaigns. null if unattributed.
firstAttribution string Source's first-touch value.
lastAttribution string Source's last-touch value.
revenueCategory string
isExistingPatient boolean
firstCallDate timestamp null if never called.
createdAt timestamp Capture time.
updatedAt timestamp
deletedAt timestamp Set when deleted at the source.
{
  "id": "c8841",
  "connectionId": "int_4a1e7c0b8d62f350",
  "contactId": "crm-55102",
  "patientId": "7734",
  "patientConnectionId": "int_9f8c2b415d3e4a17",
  "employeeId": "4821",
  "bannerId": 33,
  "channel": "Meta",
  "campaignId": "98431",
  "firstAttribution": "Facebook Lead Ad",
  "lastAttribution": "Facebook Lead Ad",
  "revenueCategory": "MedSpa",
  "isExistingPatient": false,
  "firstCallDate": "2026-05-12T16:42:00Z",
  "createdAt": "2026-05-12T14:05:00Z",
  "updatedAt": "2026-05-13T06:15:00Z",
  "deletedAt": null
}

Errors

{
  "error": {
    "code": "invalid_request",
    "message": "limit must be between 1 and 5000",
    "requestId": "req_abc123",
    "details": {
      "field": "limit"
    }
  }
}

requestId matches the X-Request-Id header. details is present when there is more to say.

Status Code Meaning
400 invalid_request Unknown parameter, malformed value, unknown connectionId, or an ambiguous single-record id.
400 invalid_cursor Cursor is invalid, expired, or does not match the request.
401 unauthenticated Missing, invalid, expired, or revoked API key.
403 forbidden Key lacks the required scope.
404 not_found Record does not exist or is outside the key's access.
429 rate_limited Rate limit exceeded.
500 internal_error Unexpected server error.
503 service_unavailable Service temporarily unavailable.
504 request_timeout Request exceeded 30 seconds. Reduce limit.

Rate Limits

Limit Default
Requests per minute 120
Requests per day 50,000

Your key's limits are in GET /access. Every response carries the per-minute window:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 93
X-RateLimit-Reset: 1778600000

X-RateLimit-Reset is the epoch second when the minute window ends. Exceeding either limit returns 429 rate_limited with a Retry-After header in seconds.

Versioning

/v1 may gain fields, enum values, optional query parameters, and endpoints without a version change. Breaking changes use a new major version path.