Primeros Pasos

Todo lo que necesita para integrarse con las APIs de Interoperabilidad de FirstMedical.

Autenticación

Cómo obtener la credencial con su clave de API

La clave de API no se envía a las APIs de datos. Se canjea por un token de acceso de corta duración, y ese token es el que acompaña cada llamada.

  1. 1

    Registre su aplicación y espere la aprobación

    Toda aplicación nueva queda en estado Pendiente. Hasta que un administrador de la plataforma la apruebe, el punto de token rechaza la clave con 401 invalid_client.

  2. 2

    Emita la clave de API

    Desde la página de su aplicación. La clave completa se muestra una sola vez: guárdela en un almacén de secretos, no en el código.

  3. 3

    Separe la clave en dos partes

    La clave tiene la forma apk_live_<prefijo>.<secreto>. Lo que va antes del punto, incluido apk_live_, es el client_id. Lo que va después es el client_secret.

  4. 4

    Canjee la clave por un token

    Envíe grant_type=client_credentials, client_id y client_secret como campos de formulario (client_secret_post) al punto de token de la plataforma. Con scope puede pedir un subconjunto de los alcances registrados para su aplicación; si lo omite, recibe todos.

    Punto de token de este entorno: https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token

    # apk_live_a1b2c3d4.e5f6...  ->  client_id=apk_live_a1b2c3d4  client_secret=e5f6...
    curl -s -X POST "https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token" \
      -d "grant_type=client_credentials" \
      -d "client_id=apk_live_a1b2c3d4" \
      -d "client_secret=e5f6..."
  5. 5

    Llame a la API con el token

    Envíe el token en la cabecera Authorization: Bearer. Hoy ninguna API de datos acepta este token de principio a fin: la exportación masiva de Provider Access se está habilitando y necesita un grupo de proveedores configurado, y los datos de Patient Access requieren que el paciente inicie sesión, algo que aún no está disponible.

El flujo de autenticación que funciona

Hoy el único flujo que emite un token es client_credentials con la clave de API, descrito arriba. Esta sección detalla la respuesta, el estado de cada API y los errores.

Tipo de concesión

client_credentials, autenticando la aplicación con el par client_id y client_secret de su clave en el cuerpo de la solicitud. Se está habilitando también la autenticación HTTP Basic con el mismo par como alternativa; mientras tanto, use los campos de formulario.

Respuesta

Un token de acceso de tipo Bearer, firmado con RS256 y con audiencia fm-interop-api. Vence a la hora (expires_in 3600). No se emite token de renovación: cuando venza, solicite uno nuevo con la misma clave.

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "provider/Export.read"
}

Provider Access

Pida el token con scope=provider/Export.read y llame a la exportación masiva del grupo con las cabeceras Authorization: Bearer, Prefer: respond-async y Accept: application/fhir+json. Un token sin ese alcance recibe 403.

# apk_live_a1b2c3d4.e5f6...  ->  client_id=apk_live_a1b2c3d4  client_secret=e5f6...
curl -s -X POST "https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=apk_live_a1b2c3d4" \
  -d "client_secret=e5f6..." \
  -d "scope=provider/Export.read"
# Kick off async export (token requested with scope=provider/Export.read)
curl -X GET "https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/provider-access/Group/{groupId}/\$export" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Prefer: respond-async" \
  -H "Accept: application/fhir+json"

# Poll until complete
curl "$CONTENT_LOCATION_URL" -H "Authorization: Bearer $ACCESS_TOKEN"

La exportación masiva requiere que su organización tenga un grupo de proveedores configurado, y esa configuración se está habilitando. Hasta entonces la llamada puede no devolver datos.

Datos de un paciente (Patient Access)

Aún no disponible. Para acceder a los datos de un paciente, el paciente debe iniciar sesión y autorizar a la aplicación (SMART App Launch), y ese paso se está habilitando. Los tokens de client_credentials no llevan contexto de paciente, así que no sirven para leer los datos de un paciente.

Errores

EstadoCódigoCausa
400invalid_requestPunto de token: Falta grant_type, o faltan client_id y client_secret.
400unsupported_grant_typePunto de token: grant_type no admitido.
400invalid_scopePunto de token: Se pidió un alcance que no está registrado para la aplicación.
401invalid_clientPunto de token: Prefijo o secreto incorrectos, clave revocada, o aplicación aún no aprobada.
403-Provider Access: El token no incluye el alcance provider/Export.read.

Cada API publica sus puntos de autorización y de token en /.well-known/smart-configuration. Consulte ese documento en el entorno que use en lugar de copiar una URL.

Probar en Sandbox →

Credenciales por flujo

Todo lo que necesita un cliente se obtiene en el portal, sin permisos de Azure ni grupos de Entra: el ID de cliente al registrar la aplicación, la clave de API o la clave pública después de la aprobación, y el grupo de exportación por atribución del pagador. La página de cada aplicación muestra estos valores con botón de copiar y qué flujos puede ejecutar.

Variables de entorno de la aplicación de ejemplo, su estado y de dónde sale cada valor
VariableDe dónde sale
SMART_CLIENT_IDEl ID de cliente de esta aplicación.
SMART_REDIRECT_URIUna URI de redirección registrada. smart-auth solo redirige a una URI registrada exactamente igual.
SMART_PATIENT_SCOPESLos scopes de paciente que registró la aplicación.
SMART_API_KEYLa clave apk_live_<prefijo>.<secreto> completa. Se muestra una sola vez al emitirla; si la perdió, emita otra y revoque la anterior.
SMART_JWK_IDEl ID de registro de su clave pública (columna Registro). Es el iss y el sub de cada aserción, no el kid ni el ID de cliente.
SMART_PRIVATE_KEYSu clave privada PKCS#8 PEM. Nunca la tiene la plataforma: es el archivo que descargó al generar el par o que creó con openssl.
SMART_ASSERTION_ALGEl algoritmo de su clave: RS384 (predeterminado), ES384 o RS256.
PROVIDER_EXPORT_GROUP_IDUn grupo que esta aplicación puede exportar. Lo asigna el pagador (atribución); vea Grupos que puede exportar.

Qué identificador va en cada lugar

  • ID de cliente (SMART_CLIENT_ID): el client_id del flujo de paciente (autorización con PKCE). Una aplicación confidencial envía además la clave de API completa como client_secret.
  • Clave de API: en client_credentials se envía dividida en el primer punto. client_id es apk_live_<prefijo> y client_secret el resto. Ese prefijo es el client_id que lleva el token emitido.
  • ID de registro de la clave (SMART_JWK_ID): el iss y el sub de la aserción private_key_jwt. Es el client_id que lleva el token emitido con clave pública.
  • kid: identifica la clave dentro de su JWKS y va en el encabezado de la aserción. No es un client_id.

Generar y registrar una clave

La forma más corta: en la página de la aplicación, Generar par de claves crea el par en su navegador con RS384 o ES384, descarga la clave privada una sola vez y registra solo la pública. El portal muestra el kid, el algoritmo y el ID de registro.

Con openssl, en su equipo. genpkey escribe PKCS#8, el formato que lee SMART_PRIVATE_KEY:

# RS384: the private key stays in private-key.pem (PKCS#8, what SMART_PRIVATE_KEY takes)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out private-key.pem
chmod 600 private-key.pem
# The public half, to upload in the portal ("Upload a file")
openssl pkey -in private-key.pem -pubout -out public-key.pem
# ES384: the private key stays in private-key.pem (PKCS#8, what SMART_PRIVATE_KEY takes)
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out private-key.pem
chmod 600 private-key.pem
# The public half, to upload in the portal ("Upload a file")
openssl pkey -in private-key.pem -pubout -out public-key.pem

Con la aplicación de ejemplo:

# In the sample app (FM.Developer.SampleApp): prints the public JWK, writes the private key with mode 600
node scripts/generate-keys.mjs ~/keys/sample-app.pem RS384

Luego suba public-key.pem en Registrar clave pública, Subir un archivo. También acepta una JWK o JWKS, un certificado X.509 (.cer o .crt) o una línea OpenSSH. Si el archivo contiene una clave privada se rechaza en el navegador y no se envía. Si usted ya publica un JWKS, registre su URL.

La aserción que firma su cliente

  • iss, sub: el ID de registro de la clave (SMART_JWK_ID), igual en ambos.
  • aud: exactamente el token_endpoint que anuncia smart-auth en /.well-known/smart-configuration: https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token
  • iat, nbf, exp: nbf e iat ahora y exp como máximo 300 segundos después. Una vida más larga se rechaza.
  • jti: un valor nuevo en cada pedido; un jti repetido se rechaza.
  • alg: RS384 o ES384 (los que exige SMART Backend Services) o RS256.

Probar las credenciales

En la página de la aplicación, Probar credenciales pide un token real con una clave de API o con su clave privada, que firma la aserción en el navegador y nunca se envía. Muestra los scopes concedidos, la vigencia y el client_id del token, o el error exacto y qué corregir.

Desde su equipo, los mismos pedidos. Ninguno imprime el token:

# The whole key in an environment variable, never on the command line history
export SMART_API_KEY='apk_live_<prefix>.<secret>'
curl -s -X POST 'https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token' \
  --data-urlencode grant_type=client_credentials \
  --data-urlencode "client_id=${SMART_API_KEY%%.*}" \
  --data-urlencode "client_secret=${SMART_API_KEY#*.}" \
  --data-urlencode 'scope=<scope>' \
  | jq '{scope, expires_in, token_type, error, error_description}'
// test-assertion.mjs   (Node 18+, no dependencies)   node test-assertion.mjs ./private-key.pem
import { readFileSync } from 'node:fs';
import { createPrivateKey, randomUUID, sign } from 'node:crypto';

const TOKEN_ENDPOINT = 'https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token';
const JWK_ID = '<SMART_JWK_ID>';   // iss and sub: the key registration id
const ALG = 'RS384';                                        // RS384, ES384 or RS256
const SCOPE = '<scope>';

const key = createPrivateKey(readFileSync(process.argv[2]));
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url');
const now = Math.floor(Date.now() / 1000);
const input = b64({ alg: ALG, typ: 'JWT' }) + '.' +
  b64({ iss: JWK_ID, sub: JWK_ID, aud: TOKEN_ENDPOINT, jti: randomUUID(), iat: now, nbf: now, exp: now + 300 });
const hash = ALG === 'RS256' ? 'sha256' : 'sha384';
const signature = sign(hash, Buffer.from(input), ALG === 'ES384' ? { key, dsaEncoding: 'ieee-p1363' } : key);

const res = await fetch(TOKEN_ENDPOINT, {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
    client_assertion: input + '.' + signature.toString('base64url'),
    scope: SCOPE,
  }),
});
const body = await res.json();
if (body.access_token) {
  const claims = JSON.parse(Buffer.from(body.access_token.split('.')[1], 'base64url'));
  console.log(res.status, { scope: body.scope, expires_in: body.expires_in, client_id: claims.client_id, client_application_id: claims.client_application_id });
} else {
  console.log(res.status, body);
}
# RS384 or RS256 only (openssl writes ECDSA signatures in DER; use the node script for ES384)
JWK_ID='<SMART_JWK_ID>'
AUD='https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/smart-auth/api/token'
KEY=./private-key.pem
b64() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
NOW=$(date +%s)
H=$(printf '{"alg":"RS384","typ":"JWT"}' | b64)
P=$(printf '{"iss":"%s","sub":"%s","aud":"%s","jti":"%s","iat":%s,"nbf":%s,"exp":%s}' \
  "$JWK_ID" "$JWK_ID" "$AUD" "$(uuidgen)" "$NOW" "$NOW" "$((NOW + 300))" | b64)
S=$(printf '%s.%s' "$H" "$P" | openssl dgst -sha384 -sign "$KEY" -binary | b64)
curl -s -X POST "$AUD" \
  --data-urlencode grant_type=client_credentials \
  --data-urlencode client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  --data-urlencode "client_assertion=$H.$P.$S" \
  --data-urlencode 'scope=<scope>' \
  | jq '{scope, expires_in, token_type, error, error_description}'

Cómo valida un administrador las claves de una aplicación

En el portal de administración, el detalle de la aplicación muestra cada clave de API y cada clave pública con su kid, algoritmo y la hora del último token emitido con ella, y los errores recientes del punto de token atribuibles a la aplicación (claves de API o registros conocidos; se guardan hasta 30 cada 10 minutos). Una clave funciona si su último token es reciente; un error dice qué falló sin leer registros del servicio.

Acceso del paciente

Un miembro autoriza a su aplicación a leer sus datos (CMS-9115-F). Código de autorización con PKCE.

Scopes que debe registrar la aplicación
launch/patient openid fhirUser offline_access patient/*.rs
Variables
SMART_CLIENT_ID, SMART_API_KEY, SMART_REDIRECT_URI, SMART_PATIENT_SCOPES
  1. Registre la aplicación con su URI de redirección exacta y los scopes de paciente.
  2. Si es confidencial, emita una clave de API: será el client_secret del intercambio de código.
  3. Envíe al miembro al authorization_endpoint con client_id, redirect_uri, scope, state, code_challenge (S256) y aud.
  4. Intercambie el código en el token_endpoint con client_id, el code_verifier y, si es confidencial, la clave de API completa como client_secret.

Lo que solo hace la plataforma: activar el inicio de sesión de miembros en el ambiente y vincular un miembro sintético a su registro.

Exportación masiva (Provider Access)

Exportación masiva de los miembros atribuidos a una organización proveedora (CMS-0057-F, Provider Access).

Scopes que debe registrar la aplicación
provider/Export.read
Variables
SMART_API_KEY | SMART_JWK_ID + SMART_PRIVATE_KEY, PROVIDER_EXPORT_GROUP_ID
  1. Registre la aplicación con provider/Export.read.
  2. Tras la aprobación, emita una clave de API o genere y registre una clave pública.
  3. En Grupos que puede exportar, use el grupo autorizado o el panel sintético de su ambiente, o solicite el vínculo con su organización por NPI.
  4. Pida un token con scope provider/Export.read y llame a GET Group/{PROVIDER_EXPORT_GROUP_ID}/$export con Prefer: respond-async.

Lo que solo hace la plataforma: atribuir miembros a la organización proveedora y aprobar el vínculo de su aplicación con ese grupo en el portal de administración. En DEV el panel sintético syntheticpanel001 está abierto a toda aplicación aprobada; en QA todavía no hay grupo configurado.

Pagador a pagador

Intercambio de pagador a pagador de un miembro que se cambió de plan (CMS-0057-F, PDEx).

Scopes que debe registrar la aplicación
payer/Exchange.read
Variables
SMART_API_KEY | SMART_JWK_ID + SMART_PRIVATE_KEY
  1. Registre la aplicación con payer/Exchange.read.
  2. Tras la aprobación, emita una clave de API o registre una clave pública.
  3. Pida un token con scope payer/Exchange.read.
  4. Llame a Patient/$member-match y luego a Patient/{id}/$everything.

Lo que solo hace la plataforma: registrar su cliente como pagador solicitante autorizado.

Autorización previa

Solicitud y consulta de autorización previa (Da Vinci PAS). La plataforma valida y reenvía; la decisión es del sistema de adjudicación.

Scopes que debe registrar la aplicación
system/Claim.c system/Claim.rs
Variables
SMART_API_KEY | SMART_JWK_ID + SMART_PRIVATE_KEY
  1. Registre la aplicación con system/Claim.c y system/Claim.rs.
  2. Tras la aprobación, emita una clave de API o registre una clave pública.
  3. Pida un token con scope system/Claim.c para enviar o system/Claim.rs para consultar.
  4. Llame a Claim/$submit o Claim/$inquire con un Bundle PAS.

Lo que solo hace la plataforma: nada más allá de aprobar la aplicación.

Documentos OpenAPI

Cada API publica su documento OpenAPI 3 en este portal, en /specs/<api>. Es la misma especificación que muestra la pestaña de documentación, con el servidor del gateway de este entorno.

Agregue ?lang=en para la descripción en inglés; sin el parámetro se entrega en español.

Conceptos básicos de FHIR R4

All APIs use FHIR R4 (v4.0.1). Responses are application/fhir+json.

  • BundleSearch results return a Bundle with entry[] array
  • OperationOutcomeErrors return an OperationOutcome resource
  • _countPagination parameter (default varies by endpoint)
  • _summary=countReturns only the total count

Límites de tasa

APILímiteVentana
Patient Access100 calls1 min
Provider Access60 calls1 min
Provider Directory200 calls1 min

Toda respuesta sujeta a límite incluye X-RateLimit-Limit y X-RateLimit-Remaining. Una respuesta 429 incluye además Retry-After, en segundos.

Clases de datos USCDI v3

The Patient Access API exposes all 17 USCDI v3 data classes required by ONC HTI-1:

Patient Demographics
Allergies
Problems/Conditions
Medications
Lab Results
Vital Signs
Procedures
Immunizations
Encounters
Clinical Notes
Goals
Care Plans
Care Teams
Coverage
Claims / EOB
SDOH (when available)
Provenance
View compliance details →

Exportación masiva de datos

The Provider Access API supports FHIR Bulk Data Access 1.0:

# Kick off async export (token requested with scope=provider/Export.read)
curl -X GET "https://apim-fmcom-interoppatientaccess-prod-001.azure-api.net/provider-access/Group/{groupId}/\$export" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Prefer: respond-async" \
  -H "Accept: application/fhir+json"

# Poll until complete
curl "$CONTENT_LOCATION_URL" -H "Authorization: Bearer $ACCESS_TOKEN"

Manejo de errores

Errors follow the FHIR OperationOutcome pattern:

{
  "resourceType": "OperationOutcome",
  "issue": [{
    "severity": "error",
    "code": "not-found",
    "diagnostics": "Resource not found"
  }]
}