CMS Blue Button

Guide to integrate with the CMS Blue Button 2.0 Medicare claims API.

CMS Blue Button 2.0 is the federal Medicare claims API. Any Medicare enrollee can authorize a third-party app to retrieve their Patient, Coverage, and Explanation of Benefit data over OAuth 2.0 / SMART on FHIR. Integrate with CMS Blue Button to enhance care coordination for Medicare beneficiaries.

This is a step-by-step guide to registering a patient-facing app on the CMS Blue Button developer sandbox, testing against CMS’s synthetic beneficiaries, and submitting for production access. Unlike Epic or Cerner where each health system registers the app, CMS Blue Button is app-global: one client_id serves every Medicare beneficiary nationwide once production access is granted.

Setup Guide

Prerequisites

If you’re serious about serving US Medicare beneficiaries, having a US company will help with the production-access review. Stripe Atlas and StartFleet can get you a registered US entity with a real address in minutes. For a US phone number, Tello gives you a virtual SIM with a real number, CMS asks for a customer support phone on the production application. Grab a domain on Namecheap and set up a business email with Google Workspace. If you’re using the Medblocks Platform, you can use app.medblocks.com as your domain and skip the domain setup entirely. Since you’ll be serving US Medicare enrollees, you’ll need US infrastructure anyway. Set up a VM in a US cloud region, install Tailscale, and use it as your exit node, this gives you a clean, stable US IP for development and testing.

You’ll also need:

  • A publicly accessible domain with HTTPS for your redirect URI, or use app.medblocks.com if you’re on the Medblocks Platform
  • A server-side backend that can handle the OAuth callback and store tokens securely (the client secret must never reach the browser)
  • A privacy policy and terms of service published at a stable URL, CMS reviews both before granting production access

Create a Developer Account

  1. Go to the CMS Blue Button Sandbox and sign up with a business email.
  2. CMS sends a verification link, confirm it before proceeding.
  3. Sign in and you’ll land on the Developer Dashboard.

CMS Blue Button developer dashboard, empty My Sandbox Apps section

Register a Sandbox Application

  1. From the Developer Dashboard, click Add an Application (or the ADD AN APPLICATION button in the hero).
  2. You land on the Register a New Application form.

Register a New Application form showing App Details required info

  1. Fill in App Details, Required Info:
  • Application Name, your product’s user-facing name. This is what beneficiaries see on the CMS consent screen and on medicare.gov’s “My Connected Apps”.
  • Callback URLs / Redirect URIs, one per line. Must start with http:// or https:// and contain no fragments (#) or query strings (?).
  • Does your application need to collect beneficiary demographic information?, Yes if you need name, address, date of birth, race, sex. Selecting Yes means the CMS consent screen surfaces a Personal Information toggle the beneficiary can uncheck.

Register every redirect URI you’ll use end-to-end, local dev, staging, and production:

If you’re on the Medblocks Platform, your backend already exposes /api/auth/smart/callback, just register the hostnames you deploy to, including https://app.medblocks.com/api/auth/smart/callback.

Optional App Information

This section is technically optional for sandbox, but filling it in now is required for production access, and it simulates what real Medicare beneficiaries will see on the consent screen, so it’s worth doing right the first time.

Optional App Information section with logo upload and description fields

  1. Upload a Logo, JPG, JPEG, or PNG, max 100 KB, max 512×512 px. This appears next to your app name on the consent screen and in the beneficiary’s connected-apps list. Use the same logo you publish elsewhere.
  2. Application Description, up to 1000 characters. Plain-English description of what your app does with Medicare data. Cover what data you pull, who sees it, and the purpose. Keep it patient-readable, this is the description CMS reviewers will compare to your actual user-facing screens during the production demo.

Organization & Policy Info

Organization and Policy Info section

Fill in:

  • Organization / Company Website URL, your company’s marketing site.
  • Privacy Policy URL, a live page covering the CMS-required sections: collection, use, sharing, retention after revocation, dormant-account handling, breach notification, and data handling on company sale. If you’re on the Medblocks Platform, docs/medblocks-privacy-policy.md in the connect-web repo is a starting template.
  • Terms of Service URL, leave blank for sandbox; required for production, point it at your hosted ToS.
  • Customer Support Email, a monitored inbox. CMS uses this address for sandbox notifications and review correspondence.
  • Customer Support Phone Number, optional for sandbox; strongly recommended for production, CMS reviewers may try to call.
  • App Development Team Contacts, comma-separated list of 2–4 engineers CMS can reach for technical issues.

Accept the Agreement and Save

Terms of Service checkbox and Save Application button

  1. Tick Yes I have read and agree to the API Terms of Service Agreement, read it; it covers attribution requirements (the “not endorsed by CMS” disclaimer) and prohibited uses (no advertising, no profiling, no sale of data).
  2. Click Save Application.

You’re redirected to the application detail page.

Save your Client ID and Client Secret

Application detail page showing App Credentials and App Details

  1. In App Credentials, click Show/Hide Credentials to reveal the Client ID and Client Secret. Copy both into your secret manager immediately, a leaked secret means re-registering the app.
  2. Plug the credentials into Medblocks:
    • Go to Settings.
    • Select Connections.
    • Find Medicare and open it.
    • Toggle Use custom credentials, paste your Client ID and Client Secret, and Save.

Medblocks Medicare connection detail with Use custom credentials toggle, Client ID, Redirect URI, and JWKS URI fields

  1. The App Details panel on the CMS dashboard confirms the configuration:
  • OAuth Client Type, confidential (because we registered a secret)
  • OAuth Grant Type, authorization-code
  • All callback URIs we registered
  • Demographic data flag, Yes

Test with Sandbox Synthetic Users

CMS provides ~30 synthetic Medicare beneficiaries (BBUser00000 through BBUser00029, passwords PW00000! through PW00029!) with pre-populated claims. No real Medicare account is required, synthetic credentials are accepted by the CMS BB sandbox login flow.

Sandbox endpoints:

Test flow:

  1. From your app, trigger a Medicare connect.
  2. The browser redirects to sandbox.bluebutton.cms.gov/v2/o/authorize/?...&aud=https://sandbox.bluebutton.cms.gov/v2/fhir/.
  3. CMS BB redirects to medicare.gov/account/login for SSO.
  4. Sign in as a synthetic user, e.g. BBUser00001 / PW00001!.
  5. On the CMS BB consent screen, click Connect.
  6. CMS redirects to your registered callback with ?code=...&state=....
  7. Your backend exchanges the code for tokens, Basic auth header with client_id:client_secret, plus code_verifier from PKCE.
  8. Token response includes access_token, refresh_token, patient (synthetic beneficiary id, e.g. -10000010254647), scope. No id_token, fetch /v2/connect/userinfo with the access token to resolve identity.
  9. Fetch Patient/{id}, Coverage?beneficiary=Patient/{id}, ExplanationOfBenefit?patient={id}. Synthetic users have 30+ EOB records each.

Known sandbox quirks:

  • Rate limits are tighter than production (~10 req/s/app). The worker must handle 429 with Retry-After-aware backoff or you’ll see “Assembly Rate Limit exceeded” errors.
  • No id_token, CMS BB does not issue id_tokens. Use the /v2/connect/userinfo endpoint instead, or use the patient claim from the token response and fetch Patient/{id} directly.

Apply for Production Access

CMS reviews every app individually before granting production credentials. The review takes 2–4 weeks and includes a live demo of the user journey.

Before contacting CMS, verify:

  • Sandbox integration works end-to-end, at least one successful Patient + Coverage + EOB pull.
  • Privacy policy is published at a stable URL and covers all CMS-required sections.
  • Terms of service is published at a stable URL.
  • Consent screen shows the inviting organization name (Shared with: <org>).
  • Beneficiary can revoke access, either an in-app disconnect button or a documented email path (e.g. privacy@yourdomain.com) with a documented 30-day deletion SLA.
  • Disconnect / revocation triggers /v2/o/revoke_token/ server-side and deletes locally retrieved Medicare data.
  • CMS attribution disclaimer (“not endorsed by CMS”) is shown on the connect screen.

Send a production access request to BlueButtonAPI@cms.hhs.gov from the customer support email you registered on the sandbox app. CMS replies within a few business days with a request form and a link to schedule a demo.

At minimum include your sandbox client_id, your privacy policy URL, your terms of service URL, the list of production redirect URIs, and a one-paragraph description of what your application does with Medicare data. Anything else CMS needs they’ll ask for via the request form.

After approval, CMS schedules a 30–45 minute Zoom demo. Have ready: a live walkthrough of beneficiary signup, consent dialog, Medicare connection, data display, and revocation; your privacy policy and ToS open in another tab; your security writeup. CMS may request follow-ups on retention, the unlink workflow, or data sharing, respond within 48 hours.

Once approved, CMS issues a production client_id and client_secret via secure file transfer. In your Medblocks dashboard, go to Settings → Connections → Medicare, switch to Custom credentials, and paste the production values. The platform repoints the FHIR endpoints to production (https://api.bluebutton.cms.gov/v2/...) automatically.

FHIR API Usage

CMS Blue Button exposes a claims-only surface, no clinical resources (no Observation, Condition, MedicationRequest, etc.). The available resources:

  • Patient, beneficiary demographics. One per beneficiary. GET /v2/fhir/Patient/{id}
  • Coverage, Parts A, B, C (Medicare Advantage), and D (Drug). GET /v2/fhir/Coverage?beneficiary=Patient/{id}
  • ExplanationOfBenefit, claims (Carrier, Inpatient, Outpatient, PDE, DME, HHA, Hospice, SNF). The bulk of the data. GET /v2/fhir/ExplanationOfBenefit?patient={id}

All responses conform to the CARIN Blue Button Implementation Guide (C4BB) profiles.

Request these scopes on authorize:

  • patient/Patient.read
  • patient/Coverage.read
  • patient/ExplanationOfBenefit.read
  • profile

profile enables the /v2/connect/userinfo endpoint. Do not request openid, CMS BB does not issue id_tokens and the scope is treated as a no-op.

Token Lifecycle

  • Access token, valid for 60 minutes. Used as Authorization: Bearer <token> on FHIR calls.
  • Refresh token, valid for 60 days from issue. Use it to mint new access tokens via grant_type=refresh_token. Each refresh issues a new refresh token (rolling).
  • Beneficiary consent, valid for 13 months from initial authorization. After 13 months the beneficiary must re-authorize from the consent screen.
  • Revocation, POST /v2/o/revoke_token/ (with token=<access or refresh token>, Basic auth header) when a beneficiary unlinks. Always returns 200 regardless of whether the token existed.

The 13-month consent window is the long pole. Even if the refresh token is still valid, calls will start returning 401 after 13 months until the beneficiary re-consents. Surface this in your UI to avoid a silent stale connection.

Common Errors and Fixes

  • invalid_client on token exchange, client secret mismatch, or PKCE verifier missing/wrong. Confirm the Client Secret in Settings → Connections → Medicare matches the CMS dashboard, and that you’re sending PKCE code_verifier alongside the secret.
  • invalid_grant on token exchange, authorization code already used, or redirect_uri does not exactly match the one used on /authorize. Codes are single-use. Make sure the callback URL passed to token exchange is byte-identical to the one on the authorize URL (case, trailing slash).
  • 429 Too Many Requests / Assembly Rate Limit exceeded, sandbox rate limits. Implement Retry-After-aware backoff. Cap concurrency per worker.
  • 404 No resources match requested URI on Patient/{id}, wrong base URL (e.g. hitting the production base while connected to sandbox). Verify the configured FHIR source matches the environment that issued the token.
  • 401 on FHIR fetch after months of success, either access token expired (refresh it) or the 13-month consent window lapsed. Refresh first; if refresh also 401s, prompt the beneficiary to re-connect Medicare.
  • Consent screen shows app name “BlueButton Client (Test - Internal Use Only)” instead of yours, using CMS’s default test app credentials, not your own. In Settings → Connections → Medicare, confirm Custom credentials is selected and the Client ID matches your sandbox app’s.