Tellescope
Use the Tellescope connector to sync up to 91 clinical and operational data streams — patients, appointments, form responses, messages, tickets, and more — into your Athena lakehouse. This is a Blotout custom connector built on the Airbyte Connector Builder (declarative manifest), auto-generated from Tellescope’s @tellescope/schema npm package.
Prerequisites
Section titled “Prerequisites”- A tag with Blotout Cloud set up (green checkmark in the Tags table)
- A Tellescope API key — generated in Tellescope under Settings → API Keys (
POST /v1/api-key). The value is shown once and cannot be retrieved later — only replaced. Ask the client to generate it under a dedicated service user, not a clinician’s account. - Agreed stream list — which of the 91 models are actually allowed to copy, confirmed in writing before any key is issued
- BAA / destination confirmation — confirmation that the destination bucket and warehouse are in PHI scope before the first sync runs
- Know the environment — production (
https://api.tellescope.com/v1) or staging (https://staging-api.tellescope.com/v1). Keys are not shared between them.
Create the pipeline
Section titled “Create the pipeline”Follow the shared Create a pipeline steps until the source catalogue appears, then select Tellescope.
Source configuration
Section titled “Source configuration”On the Source step, configure the connector:
| Field | Purpose |
|---|---|
| Schedule Type | How the pipeline runs — typically Scheduled |
| Sync Frequency | How often Airbyte syncs (for example, Every 24 hours) |
| Source Name | Name for this pipeline — used in the Athena schema <SOURCE_NAME>_<ENV> |
Authentication (API Key)
Section titled “Authentication (API Key)”Every request carries a single header: Authorization: API_KEY <api key>. Note the format is literally the word API_KEY, a space, then the key — not Bearer.
| Method | Supported | Detail |
|---|---|---|
| API key | In use | Long-lived, no refresh step. |
| Session token | Not usable | Expires (expirationInSeconds), needs interactive credential — unusable for scheduled sync. |
| OAuth / OAuth2 | Not available | Tellescope’s OAuth2 runs the other direction (connecting Tellescope to third parties like Zoom, Google, Healthie). Tellescope is not an OAuth provider for its own API. |
Configuration fields
Section titled “Configuration fields”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| api_key | secret string | Yes | — | Tellescope API key, sent as Authorization: API_KEY <key>. Generated via POST /v1/api-key (Settings → API Keys); shown once and cannot be retrieved later. |
| start_date | string | Yes | — | Earliest record modification date to pull (YYYY-MM-DD), sent as the from query param with fromToField=updatedAt. Records edited before this date are skipped even if they were created long ago. |
| limit | integer | No | 1000 | Page size (limit query param). The API caps at 1000; the connector pages with lastId until a short page arrives. |
| lookback_hours | integer | No | 1 | On each incremental sync, rewind this many hours before the stored cursor. Records are appended — deduplicate latest-per-id downstream. |
| base_url | string | No | "https://api.tellescope.com/v1" | Override for the staging environment (https://staging-api.tellescope.com/v1). Default is production. |
Supported sync modes
Section titled “Supported sync modes”The Tellescope source connector supports the following sync modes:
- Incremental Sync — Append
Uniform stream contract
Section titled “Uniform stream contract”Tellescope’s API is uniform: every model is a CRUD resource with the same list contract. All 91 streams share these properties:
| Property | Value |
|---|---|
| Primary key | id — a 24-character Mongo ObjectId |
| Cursor field | updatedAt — ISO-8601 with milliseconds, e.g. 2026-08-24T14:38:51.911Z |
| Sync mode | Incremental | Append |
| Pagination | lastId cursor on the previous page’s final record; a short page ends the stream |
| Page size | limit, default and maximum 1000 |
| API pattern | GET /v1/<plural>?limit=1000&lastId=<id>&sort=oldFirst&from=<ISO timestamp>&fromToField=updatedAt |
| Response format | Bare JSON array (no envelope, no page number, no total count) |
Every model extends Tellescope’s ClientRecord base, which guarantees id and updatedAt on all records.
The connector sends fromToField=updatedAt to repoint the filter at modification time (Tellescope defaults to createdAt), and sort=oldFirst so the lastId cursor walks forward through the filtered set.
Base fields (present on every stream)
Section titled “Base fields (present on every stream)”| Field | JSON Type | Athena Type | Notes |
|---|---|---|---|
id | string | varchar | 24-char Mongo ObjectId — primary key |
businessId | string | varchar | |
organizationIds | array<string> | array | |
sharedWithOrganizations | array<object> | array | |
creator | string | varchar | |
updatedAt | string | varchar | ISO-8601, ms precision — cursor field |
originalId | string | varchar |
Supported streams — all 91
Section titled “Supported streams — all 91”The table below lists every stream, its endpoint, field count, and PHI classification.
Patient data streams (PHI)
Section titled “Patient data streams (PHI)”| Stream | Endpoint | Fields | PHI |
|---|---|---|---|
endusers | GET /endusers | 99 | Yes |
calendar_events | GET /calendar-events | 100 | Yes |
care_plans | GET /care-plans | 17 | Yes |
chat_rooms | GET /chat-rooms | 40 | Yes |
chats | GET /chats | 32 | Yes |
database_records | GET /database-records | 9 | Yes |
email_sync_denials | GET /email-sync-denials | 8 | Yes |
emails | GET /emails | 55 | Yes |
enduser_custom_types | GET /enduser-custom-types | 11 | Yes |
enduser_eligibility_results | GET /enduser-eligibility-results | 13 | Yes |
enduser_encounters | GET /enduser-encounters | 18 | Yes |
enduser_medications | GET /enduser-medications | 35 | Yes |
enduser_observations | GET /enduser-observations | 32 | Yes |
enduser_orders | GET /enduser-orders | 30 | Yes |
enduser_problems | GET /enduser-problems | 14 | Yes |
enduser_profile_views | GET /enduser-profile-views | 13 | Yes |
enduser_status_updates | GET /enduser-status-updates | 10 | Yes |
enduser_tasks | GET /enduser-tasks | 11 | Yes |
enduser_views | GET /enduser-views | 17 | Yes |
fax_logs | GET /fax-logs | 18 | Yes |
files | GET /files | 22 | Yes |
form_responses | GET /form-responses | 59 | Yes |
forum_posts | GET /forum-posts | 17 | Yes |
group_mms_conversations | GET /group-mms-conversations | 25 | Yes |
inbox_threads | GET /inbox-threads | 29 | Yes |
meetings | GET /meetings | 12 | Yes |
notes | GET /notes | 21 | Yes |
phone_calls | GET /phone-calls | 42 | Yes |
post_comments | GET /post-comments | 18 | Yes |
purchases | GET /purchases | 22 | Yes |
sms_messages | GET /sms-messages | 49 | Yes |
suggested_contacts | GET /suggested-contacts | 10 | Yes |
superbills | GET /superbills | 12 | Yes |
ticket_thread_comments | GET /ticket-thread-comments | 30 | Yes |
ticket_threads | GET /ticket-threads | 16 | Yes |
tickets | GET /tickets | 66 | Yes |
user_notifications | GET /user-notifications | 12 | Yes |
waitlists | GET /waitlists | 11 | Yes |
Configuration and reference streams (non-PHI)
Section titled “Configuration and reference streams (non-PHI)”| Stream | Endpoint | Fields |
|---|---|---|
agent_records | GET /agent-records | 16 |
ai_conversations | GET /ai-conversations | 17 |
allergy_codes | GET /allergy-codes | 10 |
analytics_frames | GET /analytics-frames | 26 |
appointment_booking_pages | GET /appointment-booking-pages | 51 |
appointment_locations | GET /appointment-locations | 21 |
automated_actions | GET /automated-actions | 17 |
automation_steps | GET /automation-steps | 15 |
automation_triggers | GET /automation-triggers | 20 |
availability_blocks | GET /availability-blocks | 18 |
background_errors | GET /background-errors | 13 |
blocked_phones | GET /blocked-phones | 8 |
calendar_event_rsvps | GET /calendar-event-RSVPs | 12 |
calendar_event_templates | GET /calendar-event-templates | 62 |
call_hold_queues | GET /call-hold-queues | 9 |
comment_likes | GET /comment-likes | 10 |
configurations | GET /configurations | 10 |
custom_dashboards | GET /custom-dashboards | 17 |
databases | GET /databases | 12 |
diagnosis_codes | GET /diagnosis-codes | 10 |
engagement_events | GET /engagement-events | 12 |
flowchart_notes | GET /flowchart-notes | 10 |
form_fields | GET /form-fields | 34 |
form_groups | GET /form-groups | 9 |
forms | GET /forms | 81 |
forums | GET /forums | 11 |
integration_logs | GET /integration-logs | 14 |
integrations | GET /integrations | 38 |
journeys | GET /journeys | 15 |
managed_content_record_assignments | GET /managed-content-record-assignments | 9 |
managed_content_records | GET /managed-content-records | 30 |
message_template_snippets | GET /message-template-snippets | 9 |
organization_payments | GET /organization-payments | 15 |
organizations | GET /organizations | 100 |
phone_trees | GET /phone-trees | 17 |
portal_brandings | GET /portal-brandings | 17 |
portal_customizations | GET /portal-customizations | 22 |
post_likes | GET /post-likes | 9 |
prescription_routes | GET /prescription-routes | 24 |
products | GET /products | 22 |
purchase_credits | GET /purchase-credits | 12 |
role_based_access_permissions | GET /role-based-access-permissions | 14 |
superbill_providers | GET /superbill-providers | 16 |
table_views | GET /table-views | 14 |
templates | GET /templates | 24 |
ticket_queues | GET /ticket-queues | 14 |
ticket_templates | GET /ticket-templates | 18 |
time_tracks | GET /time-tracks | 27 |
user_logs | GET /user-logs | 12 |
users | GET /users | 71 |
vital_configurations | GET /vital-configurations | 13 |
webhook_logs | GET /webhook-logs | 11 |
webhooks | GET /webhooks | 11 |
Not exposed
Section titled “Not exposed”api_keys is deliberately excluded — credential metadata with no analytical value. Airbyte’s connection check runs against users (staff, non-PHI).
PHI scoping guidance
Section titled “PHI scoping guidance”The PHI flag is a heuristic: Tellescope does not label its models, and a free-text field on almost any stream (a note on a ticket, a database record, an uploaded file) can end up holding patient information. Treat it as a floor, not a guarantee.
Clearly patient data:
endusers (name, email, phone, DOB, address, external ids), chats / chat_rooms / emails / sms_messages / phone_calls / fax_logs (message bodies), form_responses (answers), notes, enduser_medications, enduser_problems, enduser_observations, enduser_encounters, care_plans, superbills, tickets / ticket_threads
Not patient records:
Reference tables (allergy_codes, diagnosis_codes), configuration (templates, journeys, automation_steps, forms, products) and staff data (users, user_logs)
Practical scoping: endusers alone is usually enough — every other model joins to it by enduserId, so analytics rarely needs the message-body streams. Start from the question being answered and add streams to the connection one at a time.
Downstream notes
Section titled “Downstream notes”- Schema:
tellescope_<client>_prod.*, one table per enabled stream updatedAtmoves on every edit and the connector appends, so the sameidlands repeatedly. Deduplicate latest-per-id downstream:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY id ORDER BY _airbyte_extracted_at DESC ) AS rn FROM tellescope_<client>_prod.endusers) WHERE rn = 1- Every record carries
businessIdandorganizationIds— filter on them if the key spans multiple organizations - Nested objects and arrays-of-objects land as JSON; flatten what you need in dbt
Regenerating the connector
Section titled “Regenerating the connector”manifest.yaml is generated, not hand-written. Tellescope publishes the machine-readable definition of every model in the public @tellescope/schema npm package, which exposes generateOpenAPISpec(). The connector’s regenerate.sh installs that package into a temp directory, dumps the spec, and rebuilds all 91 streams and their schemas from it:
cd connectors/source-tellescope/v1/./regenerate.sh # latest @tellescope/schema./regenerate.sh 1.256.10 # pin a versionA Tellescope API change is a re-run and a diff review, not a hand edit.
Setup checklist
Section titled “Setup checklist”- Agree the stream list with the client in writing, before any key is issued
- Confirm the destination bucket / warehouse is in PHI scope under the BAA
- Client generates an API key under a dedicated service user and sends it securely (shown once)
- Confirm environment — production or staging
base_url - Local smoke test:
cd connectors/source-tellescope/v1 && ./test_local.sh <key>(defaults to a small stream set;logs/read_raw.jsonlwill contain PHI — delete it after) - Publish the image:
./build_and_deploy.sh(or merge to main — CI builds it) - Airbyte → Settings → Sources → New → Add Docker Connector →
blotout/source-tellescope, tag1.0.0 - Create the source; run check and confirm discover returns 91 streams
- Create the connection and select only the agreed streams, all Incremental | Append
- Verify
fromToField=updatedAtis honoured: pick a record edited (not created) after the start date and confirm it arrives - Verify
lastIdpaging on a stream with more thanlimitrows —./test_debug.sh <key> <stream>setslimit=2to force it - Point dbt at
tellescope_<client>_prodand build the dedupe views
Changelog
Section titled “Changelog”| Version | Date | Description |
|---|---|---|
| 1.0.0 | 2026-08-26 | Initial release with 91 streams auto-generated from @tellescope/schema |
Complete Schema and Summary
Section titled “Complete Schema and Summary”Continue through the Schema and Summary steps to choose streams and confirm the connection. Save and run the first sync.
After a successful sync, Airflow creates an organization-scoped DAG for the pipeline, and dbt models feed downstream analytics.
Related documentation
Section titled “Related documentation”- Data Pipelines overview — Tags-first create flow