API Reference
The Data Co APIv0betaBeta
The Data Co API lets you sync your organization's operational data into your own backend systems, data warehouse, or analytics tools.
The API is designed for server-to-server integrations. Do not call it directly from browser or mobile frontend code.
This is a beta release. The
/v0betapath is not a stable contract and may change without a version bump. Several documented fields are not populated yet, and record identifiers will change format before/v1. Read Beta Limitations before you design your schema. Access is currently limited totdc_test_keys on the demo dataset.
Base URLs
Production: https://api.thedataco.com/v0betaAuthentication
Send your API key in the Authorization header:
Authorization: Bearer tdc_live_...Example:
curl https://api.thedataco.com/v0beta/appointments \
-H "Authorization: Bearer tdc_live_..."API keys are scoped to one organization and one environment. Test keys start with tdc_test_; production keys start with tdc_live_.
Keep API keys in a secret manager or backend environment variable. Do not commit them to source control or expose them in frontend code.
Quickstart
Check your key and permissions:
curl https://api.thedataco.com/v0beta/access \
-H "Authorization: Bearer tdc_live_..."Fetch the first page of revenue entries:
curl "https://api.thedataco.com/v0beta/revenue-entries?limit=500" \
-H "Authorization: Bearer tdc_live_..."Continue paging with pagination.nextCursor:
curl "https://api.thedataco.com/v0beta/revenue-entries?limit=500&cursor=..." \
-H "Authorization: Bearer tdc_live_..."Beta Limitations
These are the differences between the beta and the intended /v1 contract. Each
one is called out again on the field it affects.
Fields that are always null today
| Field | Resources | Why |
|---|---|---|
createdAt |
banners, clinics, employees, patients, appointments, revenue-entries | No source column yet. Populated on /payments only. |
updatedAt |
banners, clinics | No source column yet. Populated on all other resources. |
deletedAt |
all resources | Deletes are physical today; there is no tombstone to report. |
Treat these as "not yet available", not as "this record has no value". Do not add
a NOT NULL constraint downstream and do not infer anything from the null.
Deletions are not reported. Because deletedAt is always null, a record
removed at the source simply stops appearing in responses. There is no signal you
can act on. If you need deletions today, periodically re-sync a date window and
reconcile by absence. The sync recipe below documents the
deletedAt flow so your integration is ready when it lands, but the branch will
not fire during beta.
Record identifiers will change. id values on employees, patients,
appointments, revenue-entries and payments are opaque strings whose format
differs between the sandbox and production, and changes again at /v1. See
Identifiers. Store them as variable-length text, never parse
them, and be prepared to re-key at /v1.
banners and clinics do not accept updatedSince. They have no
updatedAt yet, so the parameter is rejected with 400 invalid_request. Sync
these two in full on every pass; they are small.
payments is empty in the sandbox. The demo dataset does not generate
payments, so a tdc_test_ key with payments:read gets an empty data array.
This is a gap in the demo data, not an error. Production keys return real data.
/sync-status reports null for banners and clinics, and reports the same
timestamp for all five gold-backed resources — they are rebuilt by one pipeline
run. See Data Freshness.
updatedSince does not narrow results in production yet. See the caveat in
Incremental Sync. Size your pipeline for a full daily
extract.
Response Format
List endpoints return:
{
"data": [],
"pagination": {
"nextCursor": null,
"hasMore": false,
"limit": 500
}
}Single-record endpoints return:
{
"data": {}
}Every response includes:
X-Request-Id: req_...Include this request ID when contacting support about an API request.
Pagination
All list endpoints use cursor pagination.
Query parameters:
| Parameter | Description |
|---|---|
limit |
Number of records to return. Default 500, maximum 5000. |
cursor |
Opaque cursor from the previous response. |
Ordering:
- Most resources are returned in ascending
updatedAt, thenidorder. bannersandclinicsare ordered byidalone, because they have noupdatedAtyet.
When pagination.hasMore is true, request the next page with pagination.nextCursor. When hasMore is false, the result set is complete.
Do not parse or modify cursors. A cursor is bound to the API key that issued it, the resource, and the exact filter set of the original request — reusing one with a different key or a changed filter returns 400 invalid_cursor. You may change limit mid-pagination.
Cursors are valid for at least 24 hours. If a cursor returns 400 invalid_cursor, restart the sync from your last updatedSince checkpoint.
Filtering
All list endpoints support:
| Parameter | Description |
|---|---|
id |
Filter by one or more record IDs. |
updatedSince |
Inclusive lower bound on updatedAt. ISO 8601 UTC timestamp. Not supported on banners or clinics. |
limit |
Page size. |
cursor |
Pagination cursor. |
Repeated query parameters are supported for multi-value filters:
GET /v0beta/revenue-entries?clinicId=12&clinicId=18&revenueCategory=MedSpaRepeated values for the same filter are OR'd together. Different filters are AND'd together.
For resources with business dates, use inclusive date filters:
GET /v0beta/appointments?startDate=2026-05-01&endDate=2026-05-31This means:
date >= 2026-05-01 AND date <= 2026-05-31Unknown or invalid query parameters return 400 invalid_request.
Values for string filters such as status, role or the category fields are not validated against a fixed list: an unmatched value returns an empty result set rather than an error. A typo therefore looks exactly like "no data". Check your value against Enumerated Values before concluding a filter returned nothing.
clinicId — and id on /clinics — are intersected with your key's access rather than rejected. Requesting a clinic your key cannot read returns 200 with an empty data array, not 403.
Identifiers
| Resource | id type |
Format |
|---|---|---|
banners, clinics |
integer | Stable numeric identifier, e.g. 12. |
employees, patients, appointments, revenue-entries, payments |
string | Opaque. Format is not part of the contract. |
The opaque string ids are generated differently per environment today:
- Sandbox (
tdc_test_keys): UUIDs, e.g."9f8c2b41-5d3e-4a17-9c62-8e0b7a1d4f35". - Production (
tdc_live_keys): composite<integration>-<source id>strings, e.g."16-4821".
Both formats change at /v1, when ids become the source system's own identifier.
Design for this:
- Store ids as variable-length text (
TEXT/VARCHARwith no tight limit). Do not size a column from the sandbox's 36-character UUIDs. - Never parse an id or derive meaning from its parts.
- Never sort on an id expecting chronological order.
- Expect to re-key your tables once at
/v1. Keep your own surrogate key.
invoiceId on revenue entries is an opaque grouping field with the same caveats. There is no /invoices endpoint in v1.
Incremental Sync
Use updatedSince for ongoing syncs. updatedSince is inclusive.
Recommended initial sync:
- Request each resource without
updatedSince. - Page until
pagination.hasMoreisfalse. - Upsert records by
id. - Store the maximum
updatedAtseen per resource.
Recommended ongoing sync:
- Request each resource with
updatedSince=<last checkpoint>. - Page until complete.
- Upsert records by
id. - If
deletedAtis notnull, treat the record as deleted or inactive downstream. Beta:deletedAtis alwaysnull, so this branch never fires yet. Implement it now so you are ready when tombstones land. - Store the new maximum
updatedAtseen.
banners and clinics have no updatedAt and reject updatedSince; re-fetch them in full on each pass.
Because updatedSince is inclusive, records can appear again across sync runs. Upsert by id and make your sync idempotent.
Beta caveat — sandbox and production
updatedAtdiffer today.In the sandbox (
tdc_test_keys),updatedAtis derived from each record's business date, soupdatedSincebehaves like a real historical feed and you can develop and test an incremental sync against it.In production,
updatedAtcurrently advances on every nightly rebuild rather than on actual content change. A productionupdatedSincerequest will therefore return every record your key can read, every day — not just changed ones. Size your pipeline for a full daily extract, not for the sandbox's delta volume.This is the change-detection work tracked as the
/v1launch gate. When it lands, productionupdatedAtbecomes content-driven and the two converge. Until then, treatupdatedSincein production as an optimization hint, not as a guarantee that the response is small.
updatedAt is never backdated — corrections always receive a new, later updatedAt — so checkpointing on the maximum value seen never misses changes. As an alternative, you can record each resource's latestSyncAt from /v0beta/sync-status before starting a sync pass and use it as the next updatedSince.
References between resources (for example an appointment's patientId) can occasionally point to a record you have not synced yet. Tolerate the missing reference; it will resolve on the next sync.
Use startDate and endDate for business-date extracts, not as a replacement for incremental sync.
Data Freshness
Use /sync-status to check the latest sync time for resources your key can read:
curl https://api.thedataco.com/v0beta/sync-status \
-H "Authorization: Bearer tdc_live_..."Example response:
{
"data": [
{
"resource": "appointments",
"latestSyncAt": "2026-05-12T06:15:00Z"
},
{
"resource": "revenue-entries",
"latestSyncAt": "2026-05-12T06:15:00Z"
}
]
}Notes:
- Only resources your key holds a read scope for are listed. A key with no read scopes gets
403 forbidden. employees,patients,appointments,revenue-entriesandpaymentsare rebuilt by a single pipeline run, so they always report the samelatestSyncAt. Do not read a per-resource difference into it.- Beta:
bannersandclinicsreportlatestSyncAt: null— they are maintained manually and have no sync timestamp.
Data is available after source syncs and normalization complete. Freshness varies by connected source and resource.
Data Types
| Type | Format |
|---|---|
| Date | YYYY-MM-DD |
| Timestamp | ISO 8601 UTC, for example 2026-05-12T14:30:00Z |
| Decimal and money | JSON string, for example "1250.00" |
| Integer | JSON number |
| Boolean | JSON boolean |
| Empty optional value | null |
All monetary values are in USD.
Enumerated Values
These fields draw from fixed value sets. Values are case-sensitive in filters.
gender (patients)
Male, Female, Not Specifiedstatus (appointments)
scheduled, confirmed, cancelled, rescheduled, no_show, completed, in_progress, unknownrole (employees)
Unassigned, Aesthetician, Aesthetician - All Devices, Consultant, Dermatologist,
Doctor, Hybrid Injector, Injector, Laser Technician, Nurse, Nurse Injector,
Support Staff, Surgeon, Therapist, WellnessparentCategory (appointments, revenue-entries)
Adjustment, Consult, Dermatology, Energy Device, Gift Card, Injectable,
Membership, Non-Clinical, Other, Other Clinical, Retail, Surgery, WellnessserviceCategory (appointments, revenue-entries)
Currently a single value, Botox. This field is sparsely populated and is expected to expand; do not build required logic on it during beta.
subCategory (appointments, revenue-entries)
A large open list (200+ values, e.g. Neurotoxin, Filler, Laser Hair Removal, Weight Loss) that grows as new source data is normalized. Discover the values present in your own data rather than hardcoding a list.
revenueCategory, status/paymentMethod/paymentCategory (payments), and region (clinics) are free-form strings sourced per organization. Discover them from your own data.
Permissions
API keys have read scopes. You can only access endpoints included in your key's scopes.
Available v1 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 |
Keys may also be restricted to specific clinics or banners. If your key is restricted, responses only include records within your allowed access. Restrictions resolve to a set of allowed clinics: clinics granted directly, plus all clinics in granted banners (including clinics added to those banners later). Appointments, revenue entries, and payments are limited to allowed clinics; you can also see any banner that contains an allowed clinic. Employees and patients are organization-wide in v1.
Restricted keys additionally receive null for the patient fields lifetimeRevenue and firstRevenueDate, which aggregate revenue across all clinics.
Endpoints
GET /access
Returns the current API key context. Requires no scope.
| Field | Type | Notes |
|---|---|---|
keyId |
string | ak_-prefixed key identifier. Not the secret. |
name |
string | Display name of the key. |
environment |
string | live or test. |
organization.id |
string | Opaque organization identifier. |
organization.name |
string | |
scopes |
string[] | Granted read scopes. |
clinicIds |
integer[] | Clinic restrictions. Empty with bannerIds means unrestricted. |
bannerIds |
integer[] | Banner restrictions. |
rateLimit.requestsPerMinute |
integer | |
rateLimit.requestsPerDay |
integer | |
expiresAt |
timestamp | null if the key does not expire. |
Example response:
{
"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": [],
"rateLimit": {
"requestsPerMinute": 120,
"requestsPerDay": 50000
},
"expiresAt": null
}
}GET /sync-status
Returns sync status for resources your key can read. See Data Freshness.
| Field | Type | Notes |
|---|---|---|
resource |
string | URL-style resource name, e.g. revenue-entries. |
latestSyncAt |
timestamp | Beta: null for banners and clinics. Identical across the five gold-backed resources. |
GET /banners
Required scope: banners:read
Filters:
| Parameter | Description |
|---|---|
id |
One or more banner IDs (integers). |
updatedSince is not supported and returns 400 invalid_request.
Response object:
| Field | Type | Notes |
|---|---|---|
id |
integer | |
name |
string | |
createdAt |
timestamp | Beta: always null. |
updatedAt |
timestamp | Beta: always null. |
deletedAt |
timestamp | Beta: always null. |
{
"id": 3,
"name": "Austin MedSpa Group",
"createdAt": null,
"updatedAt": null,
"deletedAt": null
}Single-record lookup:
GET /v0beta/banners/{id}GET /clinics
Required scope: clinics:read
Filters:
| Parameter | Description |
|---|---|
id |
One or more clinic IDs (integers). Intersected with your key's access — an id you cannot read is omitted, not rejected. |
bannerId |
One or more banner IDs. |
updatedSince is not supported and returns 400 invalid_request.
Response object:
| Field | Type | Notes |
|---|---|---|
id |
integer | |
name |
string | |
bannerId |
integer | null if the clinic has no banner. |
address.line1 |
string | Street address. |
address.city |
string | |
address.state |
string | |
address.postalCode |
string | |
address.country |
string | |
region |
string | Free-form, org-specific. |
currency |
string | |
timezone |
string | IANA name, e.g. America/Chicago. |
createdAt |
timestamp | Beta: always null. |
updatedAt |
timestamp | Beta: always null. |
deletedAt |
timestamp | Beta: always null. |
{
"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": null,
"updatedAt": null,
"deletedAt": null
}Single-record lookup:
GET /v0beta/clinics/{id}GET /employees
Required scope: employees:read
Filters:
| Parameter | Description |
|---|---|
id |
One or more employee IDs. |
updatedSince |
Inclusive update timestamp lower bound. |
active |
true or false. |
role |
One or more roles. See Enumerated Values. |
Response object:
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque. See Identifiers. |
fullName |
string | |
active |
boolean | |
role |
string | null if unassigned. Enumerated. |
firstActivityDate |
date | |
createdAt |
timestamp | Beta: always null. |
updatedAt |
timestamp | |
deletedAt |
timestamp | Beta: always null. |
{
"id": "16-4821",
"fullName": "Jane Smith",
"active": true,
"role": "Nurse Injector",
"firstActivityDate": "2024-03-12",
"createdAt": null,
"updatedAt": "2026-05-12T14:30:00Z",
"deletedAt": null
}Single-record lookup:
GET /v0beta/employees/{id}GET /patients
Required scope: patients:read
The v1 patient object does not include patient names or date of birth.
Filters:
| Parameter | Description |
|---|---|
id |
One or more patient IDs. |
updatedSince |
Inclusive update timestamp lower bound. |
Response object:
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque. See Identifiers. |
gender |
string | Male, Female or Not Specified. |
firstActivityDate |
date | |
firstRevenueDate |
date | null for clinic- or banner-restricted keys. |
lifetimeRevenue |
money | null for clinic- or banner-restricted keys. |
createdAt |
timestamp | Beta: always null. |
updatedAt |
timestamp | |
deletedAt |
timestamp | Beta: always null. |
{
"id": "16-7734",
"gender": "Female",
"firstActivityDate": "2024-03-12",
"firstRevenueDate": "2024-03-20",
"lifetimeRevenue": "1250.00",
"createdAt": null,
"updatedAt": "2026-05-12T14:30:00Z",
"deletedAt": null
}Patients are organization-wide even for restricted keys, but firstRevenueDate and lifetimeRevenue are null for them because those values aggregate revenue across clinics the key cannot read.
Single-record lookup:
GET /v0beta/patients/{id}GET /appointments
Required scope: appointments:read
Filters:
| Parameter | Description |
|---|---|
id |
One or more appointment IDs. |
updatedSince |
Inclusive update timestamp lower bound. |
startDate |
Inclusive date lower bound. |
endDate |
Inclusive date upper bound. |
clinicId |
One or more clinic IDs. Intersected with your key's access. |
employeeId |
One or more employee IDs. |
patientId |
One or more patient IDs. |
status |
One or more statuses. See Enumerated Values. |
parentCategory |
One or more parent categories. |
subCategory |
One or more subcategories. |
serviceCategory |
One or more service categories. |
revenueCategory |
One or more revenue categories. |
Response object:
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque. See Identifiers. |
date |
date | Clinic-local business date. |
clinicId |
integer | |
employeeId |
string | null if unassigned. |
patientId |
string | null if unassigned. |
startTime |
timestamp | UTC. |
endTime |
timestamp | UTC. |
durationHours |
decimal | Hours, e.g. "1.00". |
status |
string | Enumerated. |
parentCategory |
string | Enumerated. |
subCategory |
string | Open list. |
serviceCategory |
string | Sparsely populated. |
revenueCategory |
string | Free-form. |
createdAt |
timestamp | Beta: always null. Intended to carry the booking time. |
updatedAt |
timestamp | |
deletedAt |
timestamp | Beta: always null. |
{
"id": "16-90114",
"date": "2026-05-12",
"clinicId": 12,
"employeeId": "16-4821",
"patientId": "16-7734",
"startTime": "2026-05-12T14:00:00Z",
"endTime": "2026-05-12T15:00:00Z",
"durationHours": "1.00",
"status": "completed",
"parentCategory": "Injectable",
"subCategory": "Neurotoxin",
"serviceCategory": "Botox",
"revenueCategory": "MedSpa",
"createdAt": null,
"updatedAt": "2026-05-12T14:30:00Z",
"deletedAt": null
}date is the clinic-local business date of the appointment; startTime and endTime are UTC timestamps. Near midnight they can appear to disagree — startDate/endDate filters apply to date.
Single-record lookup:
GET /v0beta/appointments/{id}GET /revenue-entries
Required scope: revenue_entries:read
Filters:
| Parameter | Description |
|---|---|
id |
One or more revenue entry IDs. |
updatedSince |
Inclusive update timestamp lower bound. |
startDate |
Inclusive date lower bound. |
endDate |
Inclusive date upper bound. |
clinicId |
One or more clinic IDs. Intersected with your key's access. |
employeeId |
One or more employee IDs. |
patientId |
One or more patient IDs. |
invoiceId |
One or more invoice IDs. |
parentCategory |
One or more parent categories. |
subCategory |
One or more subcategories. |
serviceCategory |
One or more service categories. |
revenueCategory |
One or more revenue categories. |
Response object:
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque. See Identifiers. |
date |
date | Business date of the revenue. |
clinicId |
integer | |
employeeId |
string | null if unassigned. |
patientId |
string | null if unassigned. |
invoiceId |
string | Opaque grouping field. No /invoices endpoint in v1. |
revenue |
money | |
total |
money | |
quantity |
decimal | |
discount |
money | |
tax |
money | |
parentCategory |
string | Enumerated. |
subCategory |
string | Open list. |
serviceCategory |
string | Sparsely populated. |
revenueCategory |
string | Free-form. |
createdAt |
timestamp | Beta: always null. |
updatedAt |
timestamp | |
deletedAt |
timestamp | Beta: always null. |
{
"id": "16-552310",
"date": "2026-05-12",
"clinicId": 12,
"employeeId": "16-4821",
"patientId": "16-7734",
"invoiceId": "16-INV-88213",
"revenue": "950.00",
"total": "1000.00",
"quantity": "1.00",
"discount": "50.00",
"tax": "0.00",
"parentCategory": "Injectable",
"subCategory": "Neurotoxin",
"serviceCategory": "Botox",
"revenueCategory": "MedSpa",
"createdAt": null,
"updatedAt": "2026-05-12T14:30:00Z",
"deletedAt": null
}Single-record lookup:
GET /v0beta/revenue-entries/{id}GET /payments
Required scope: payments:read
Beta: the demo dataset generates no payments, so this endpoint returns an empty data array for tdc_test_ keys.
Filters:
| Parameter | Description |
|---|---|
id |
One or more payment IDs. |
updatedSince |
Inclusive update timestamp lower bound. |
startDate |
Inclusive paymentDate lower bound. |
endDate |
Inclusive paymentDate upper bound. |
clinicId |
One or more clinic IDs. Intersected with your key's access. |
patientId |
One or more patient IDs. |
status |
One or more payment statuses. |
paymentMethod |
One or more payment methods. |
paymentCategory |
One or more payment categories. |
Response object:
| Field | Type | Notes |
|---|---|---|
id |
string | Opaque. See Identifiers. |
status |
string | Free-form, org-specific. |
total |
money | |
paymentDate |
date | What startDate/endDate filter on. |
effectiveDate |
date | |
paymentMethod |
string | Free-form, org-specific. |
paymentCategory |
string | Free-form, org-specific. |
clinicId |
integer | |
patientId |
string | |
createdAt |
timestamp | Populated. The only resource with a real createdAt today. |
updatedAt |
timestamp | |
deletedAt |
timestamp | Beta: always null. |
{
"id": "16-771204",
"status": "paid",
"total": "1000.00",
"paymentDate": "2026-05-12",
"effectiveDate": "2026-05-12",
"paymentMethod": "Credit Card",
"paymentCategory": "Patient Payment",
"clinicId": 12,
"patientId": "16-7734",
"createdAt": "2026-05-12T14:05:00Z",
"updatedAt": "2026-05-12T14:30:00Z",
"deletedAt": null
}startDate/endDate filter on paymentDate, not effectiveDate.
Single-record lookup:
GET /v0beta/payments/{id}Batch Lookup By ID
Every list endpoint supports repeated id query parameters:
GET /v0beta/appointments?id=16-90114&id=16-90115Maximum IDs per request: 100. Exceeding it returns 400 invalid_request.
The response uses the normal list envelope. Nonexistent or inaccessible IDs are omitted from data.
Error Responses
Errors use this format:
{
"error": {
"code": "invalid_request",
"message": "limit must be between 1 and 5000",
"requestId": "req_abc123",
"details": {
"field": "limit"
}
}
}details is omitted when there is nothing to add. requestId matches the X-Request-Id response header.
Common status codes:
| Status | Code | Meaning |
|---|---|---|
400 |
invalid_request |
Invalid query parameter, malformed value, or unsupported filter. |
400 |
invalid_cursor |
Cursor is invalid, expired, issued to another key, or does not match the request filters. |
401 |
unauthenticated |
Missing or invalid API key. |
403 |
forbidden |
API key does not have the required scope. |
404 |
not_found |
Record was not found or is outside the key's allowed access. |
429 |
rate_limited |
Rate limit exceeded. |
500 |
internal_error |
Unexpected server error. |
503 |
service_unavailable |
Service temporarily unavailable. |
504 |
request_timeout |
Request exceeded the maximum processing time. |
All invalid_cursor responses are identical regardless of the underlying cause; the remedy is always to drop the cursor and restart from your last checkpoint.
Requests have a maximum processing time of 30 seconds. If a request times out, reduce limit or narrow the filters.
Rate Limits
Default limits:
| Limit | Default |
|---|---|
| Requests per minute | 120 |
| Requests per day | 50,000 |
Higher limits may be available by contract. Your key's actual limits are in GET /access.
Every response carries the current per-minute window state:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 93
X-RateLimit-Reset: 1778600000X-RateLimit-Reset is the epoch-seconds time at which the current minute window ends. These three headers always describe the minute window — the daily limit is not represented in them.
Exceeding either limit returns 429 rate_limited with a Retry-After header giving the seconds until the binding window ends. Back off for that long rather than retrying immediately.
Security
API keys provide access to scoped operational data for your organization.
Recommended practices:
- Store keys in a secret manager or backend environment variable.
- Do not expose keys in browser, mobile, or desktop client code.
- Do not commit keys to source control.
- Use the least-privileged scopes needed for the integration.
- Use staging keys and demo data while developing.
- Rotate keys periodically or when team access changes.
- Contact The Data Co immediately if a key is exposed or compromised.
Changelog Policy
V1 may add new fields, enum values, optional filters, and endpoints without a version change. New endpoints require explicit scopes before your key can access them.
Breaking changes will use a new major version path.
During beta, /v0beta may change without a version bump — see Beta Limitations.