ChartHeroAI

DocsDeveloper Docs

Launch

Launch Flow

Understand the end-to-end flow from encrypted browser launch URL to ChartHero's launch-context callback.

v1.0

The launch round trip

Your system

Mint the launch link

Your backend mints an encrypted launch URL carrying an opaque launch_state and optional actor evidence.

ChartHero

Validate the link

ChartHero decrypts ctx and checks issuer, audience, expiry, key id, and replay before any patient opens.

ChartHero

Call your adapter back

ChartHero calls POST /v1/launch/context with your launch_state and the resolved actor.

Your system

Answer in ChartHero’s schema

You return exactly one subject: patient, or encounter with required encounter.patient, plus optional setup_defaults.

A launch is a four-step round trip between your system and ChartHero. You mint an encrypted link, ChartHero validates it, ChartHero calls you back for the real context, and you answer in ChartHero's schema.

Everything ChartHero needs to open a patient lives in your answer to step 4, not in the link. The link carries an opaque, adapter-owned handle plus optional proof of who is launching — and the whole payload is encrypted, so nothing readable is ever exposed in the browser or in URLs. You stay in control of what context a launch resolves to.

Your backend (or a trusted link issuer) builds an encrypted launch URL. It has one moving part: a compact JWE in the ctx query parameter.

Text
https://app.charthero.ai/launch/{launch_alias}?ctx=<compact-jwe>

Inside the encrypted payload you put two things that matter: an opaque launch_state, and — optionally — actor evidence for who is opening the link.

JS
const payload = {
  iss: 'acme-care-platform',
  aud: 'charthero',
  iat: now,
  exp: now + 300,            // links live at most 5 minutes
  jti: '01JZ123456789ABCDEFGHJKLM', // unique per launch; comes back as launch_id

  // Opaque handle you control — see "Your launch_state is a scratchpad" below
  launch_state: {
    appointment_id: 'apt-789',
    correlation_id: 'partner-click-123',
  },

  // Optional — see "Actor validation works both ways" below
  actor: {
    email: 'clinician@example.com',
  },
};

launch_state is yours — put whatever your adapter needs to rebuild the context when ChartHero calls you back. It travels encrypted inside the launch link and ChartHero never interprets it, so you can carry the real values your adapter needs. Two practical limits: keep it within the size cap (~4 KB — it rides inside the encrypted URL), and avoid long-lived secrets or credentials.

Your launch_state is a scratchpad

launch_state is yours. ChartHero never reads or interprets it — it stores it, then hands it back to you verbatim in step 3.

Treat it as the note you leave yourself between when a user clicks launch and when ChartHero calls you back. Put whatever you'll need to reconstruct the right context: an appointment row id, an internal encounter key, a correlation id. When the callback arrives, you resolve that handle against your own system — applying your own authorization and workflow rules — and build the response.

ChartHero never acts on launch_state — so anything ChartHero must actually open (the patient, demographics, setup) still has to come back in your step-4 response. That's why a compact handle like { "appointment_id": "apt-789" } is usually all you need: you're just leaving yourself enough to look the rest up. Carrying more is fine when it saves you a round trip; it simply won't change what ChartHero opens.

Actor validation works both ways

Building a basic patient + appointment adapter workflow? You can skip this entirely. Send no actor and run provider linking disabled. Actor evidence and provider linking are conditional — add them only when your authorization model needs ChartHero to verify or map the launching user. The rest of this section is for when you do.

The optional actor block is evidence about who should be opening this link. You can identify them three ways, alone or in combination:

You send ChartHero matches it against
actor.actor_id The ChartHero public user id
actor.email A verified ChartHero identity (case-insensitive)
actor.external_ids[] The user's mapped provider linkage

In practice, email or provider external_ids[] is the normal partner path — you usually know those at click time. actor.actor_id is only useful when you already hold the ChartHero public user id, which most partners won't.

Matching is strict: if you send more than one criterion, every criterion must match the authenticated ChartHero user, or the launch is rejected before any patient context opens. This is how you assert "this link is for Dr. Smith" and have ChartHero enforce it for you.

In disabled and optional modes you don't have to send any of it. If you omit actor, ChartHero still resolves the authenticated user and sends the enriched actor back to you in step 3 — so you can run your own validation on your side if you prefer to own that decision. Either way, you learn who opened the link.

If provider linking is required, this is the exception: you must include at least one actor criterion (actor_id, email, or external_ids[]) in the payload — ChartHero rejects the launch otherwise — and it additionally requires a durable provider mapping before it ever calls you. If provider linking is disabled, omit external_ids[]. See Provider Linking.

When the browser hits the launch URL, ChartHero — not your server — does the gatekeeping. It resolves the launch alias to your installation, decrypts ctx, and checks iss, aud, iat, exp, kid, and replays the jti. Expired or replayed links are rejected here, before your adapter is ever called.

Step 3 — ChartHero calls you back

Once the link is valid, ChartHero calls your adapter with the opaque state and the resolved actor:

Text
POST {base_url}/v1/launch/context
JSON
{
  "launch_state": { "appointment_id": "apt-789", "correlation_id": "partner-click-123" },
  "actor": {
    "actor_type": "user",
    "actor_id": "11111111-1111-4111-8111-111111111111",
    "email": "clinician@example.com",
    "display_name": "Dr. Chris Smith",
    "active": true,
    "external_ids": [{ "source": "provider_acme_care_prod", "type": "provider_id", "value": "prov-456" }]
  },
  "launch_id": "01JZ123456789ABCDEFGHJKLM"
}

launch_state is your scratchpad from step 1, handed back untouched. actor is the enriched ChartHero user (not the raw evidence you sent) — present whenever an authenticated user is resolved, even if you sent no actor evidence.

launch_id is the same value you minted as jti in step 1. It's named jti inside the encrypted token (the standard JWT claim) and surfaced as launch_id in this callback body. Use it for idempotency and replay-safe logging.

Step 4 — You answer in ChartHero's schema

You resolve launch_state in your system and return the context ChartHero should open. This is the authoritative context check — ChartHero opens exactly what you return here.

This holds even in the simplest case, where you already have the patient and appointment IDs at click time: they still must come back in this response. ChartHero opens what the response returns, never what was in the link — so returning the IDs here isn't redundant, it's the only thing that opens a patient.

JSON
{
  "encounter": {
    "external_ids": [{ "source": "acme_care_prod", "type": "appointment_id", "value": "apt-789" }],
    "patient": {
      "external_ids": [{ "source": "acme_care_prod", "type": "patient_id", "value": "pat-123" }]
    },
    "start_at": "2026-06-01T14:00:00Z"
  },
  "setup_defaults": {
    "mode": "phone_call",
    "template_id": "0000...0013",
    "phone_call": { "to_e164": "+15551234567" }
  }
}

Three parts, and the relationships between them are the point:

  • Subject context (required). Return exactly one top-level subject: encounter for an encounter/appointment launch, or patient for a patient-only launch. A patient-only response creates a new ChartHero encounter without an external encounter reference.
  • encounter launches include encounter.patient. When you return encounter context, the embedded patient is required and asserts that the patient and appointment belong together. Your adapter must verify that relationship before returning success; if the launch state points to a different pairing, return 409 identifier_mismatch rather than open the wrong patient/appointment.
  • setup_defaults (optional). Configures how the ChartHero workflow opens — the mode, the template_id, the phone-call target. This is your hook to land the user in the right UI state instead of a blank workflow. Omit it to let ChartHero use its own defaults.

This is the full appointment path: launching with an appointment means resolving it from launch_state and returning it as the encounter here. Returning an appointment ChartHero can query directly (appointment search) is a separate capability that is not yet self-service enabled — see Configuration.

Repeat launches don't fragment your data. ChartHero links the encounter by the exact configured encounter identifier you return. If the same appointment is launched again later, ChartHero reuses the existing encounter rather than creating a new one — so a user clicking the same appointment's link on Monday and again on Wednesday lands in one encounter, not two.

If you can't resolve the launch, deny it with a stable error: 404 patient_not_found, 404 appointment_not_found, 403 permission_denied, or 409 identifier_mismatch.

FAQ

Can launch_state contain anything? Yes — it's an opaque JSON object you own that ChartHero never interprets, so you can carry whatever your adapter needs to rebuild context, including real values. It travels encrypted inside the launch link. Just keep it within the ~4 KB size cap and skip long-lived secrets.

Do I have to tell ChartHero who's launching? No. Sending actor lets ChartHero enforce identity for you. If you skip it, ChartHero still returns the resolved actor in step 3 so you can validate yourself.

How long is a launch link valid? At most five minutes (exp - iat ≤ 300s), single-use — the jti is replay-protected.

Where do patient demographics, encounter times, and setup go? In your step-4 response — that's the only context ChartHero acts on. You can also keep copies in launch_state for your own round-trip use, but ChartHero won't read them.

What configures the ChartHero UI on open? setup_defaults in the step-4 response (mode, template_id, phone_call).

Next steps

ChartHeroAI

Developer documentation for ChartHero APIs, adapters, and integrations.

HIPAA-aligned safeguards

BAA available

ChartHero AI LLC