Authentication

OAuth 2.0 token endpoint for SMART Backend Services.

This reference is generated from the canonical Huli Public API OpenAPI specification — do not edit it directly.

Endpoints

Request Access Token

OAuth 2.0 token endpoint implementing the SMART Backend Services client_credentials grant with private_key_jwt client authentication. The client must submit a signed JWT assertion (JWS, RS384) containing:

  • iss and sub: your client_id (API key prefix)
  • aud: this token endpoint URL
  • exp: expiration (max 5 minutes from now)
  • iat: issued-at timestamp
  • jti: unique token ID (for replay prevention) The server validates the assertion signature against the client's registered JWKS endpoint, checks claim validity, and issues a short-lived access token (5-minute TTL). Rate limited to 20 requests per minute per source IP.
POST/auth/token

Request Body

nametyperequireddescription
grant_typestringrequiredMust be `client_credentials`.
client_assertion_typestringrequiredMust be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
client_assertionstringrequiredRSA-signed JWT (JWS, RS384) containing the required claims.
scopestringoptionalSpace-separated list of requested FHIR scopes. If omitted, all scopes registered for the API key are granted.

Response — 200

{
  "access_token": "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 300,
  "scope": "system/Patient.rs system/Appointment.rs"
}

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X POST https://api.huli.ai/auth/token \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/fhir+json" \
  -d @token.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/auth/token`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/fhir+json",
    },
    body: JSON.stringify(payload),
  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}

resp = requests.post(
    f"https://api.huli.ai/auth/token",
    headers=headers,
    json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class createTokenExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");
        String payload = "{}"; // your serialized FHIR resource

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/auth/token"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .header("Content-Type", "application/fhir+json")
        .method("POST", HttpRequest.BodyPublishers.ofString(payload))
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func createTokenExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("POST", "https://api.huli.ai/auth/token", body)
    req.Header.Set("Authorization", "Bearer "+token)
    req.Header.Set("Content-Type", "application/fhir+json")
    // set req.Body to your serialized resource
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

Errors

CodeStatusDescription
HPB-00101400Validation error
HPB-00106401Authentication failed
HPB-00105429Rate limit exceeded

Introspect a bearer credential

Returns non-sensitive metadata about the bearer credential in the Authorization header — in particular whether it is a sandbox or a production key. Used by the interactive playground to refuse production credentials before running any request from a browser (sandbox keys only). Carries no PHI. Rate limited per source IP.

GET/auth/introspect

Response — 200

{
  "mode": "sandbox",
  "org_id": "0193b7c8-d4e7-7000-8000-000000009001",
  "key_prefix": "htk_ab12",
  "expires_on": "2026-07-28T00:00:00Z",
  "volume_cap": 10000,
  "scopes": [
    "system/Patient.cru"
  ]
}

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X GET https://api.huli.ai/auth/introspect \
  -H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/auth/introspect`,
  {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${token}`,
    },

  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}

resp = requests.get(
    f"https://api.huli.ai/auth/introspect",
    headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class introspectTokenExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/auth/introspect"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .method("GET", HttpRequest.BodyPublishers.noBody())
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func introspectTokenExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("GET", "https://api.huli.ai/auth/introspect", nil)
    req.Header.Set("Authorization", "Bearer "+token)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

Errors

CodeStatusDescription
HPB-00106401Authentication failed
HPB-00105429Rate limit exceeded

Revoke Access or Refresh Token

OAuth 2.0 token revocation endpoint (RFC 7009). Revokes an access token or refresh token previously issued by /auth/token or the interactive Authorization Code + PKCE flow. Returns 200 even when the token is already revoked or unknown, as required by the RFC.

POST/auth/revoke

Request Body

nametyperequireddescription
tokenstringrequiredThe access or refresh token to revoke.
token_type_hintstringoptionalOptional hint about the token type.

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X POST https://api.huli.ai/auth/revoke \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/fhir+json" \
  -d @revoke.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/auth/revoke`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/fhir+json",
    },
    body: JSON.stringify(payload),
  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}

resp = requests.post(
    f"https://api.huli.ai/auth/revoke",
    headers=headers,
    json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class revokeTokenExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");
        String payload = "{}"; // your serialized FHIR resource

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/auth/revoke"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .header("Content-Type", "application/fhir+json")
        .method("POST", HttpRequest.BodyPublishers.ofString(payload))
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func revokeTokenExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("POST", "https://api.huli.ai/auth/revoke", body)
    req.Header.Set("Authorization", "Bearer "+token)
    req.Header.Set("Content-Type", "application/fhir+json")
    // set req.Body to your serialized resource
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

OAuth Authorization Endpoint (Interactive)

Interactive OAuth 2.1 Authorization Code + PKCE entry point for the user-facing consent flow. Redirects the browser to the Huli consent UI; on consent (or denial) the server redirects back to the registered redirect_uri with the appropriate authorization code or error query parameter. Requires code_challenge (S256). Public clients use this flow alongside client_credentials for M2M; both surfaces share the same access-token format.

GET/authorize

Query Parameters

nametyperequireddescription
response_typestringrequired
client_idstringrequired
redirect_uristringrequired
scopestringrequired
statestringrequired
code_challengestringrequired
code_challenge_methodstringrequired

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X GET https://api.huli.ai/authorize \
  -H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/authorize`,
  {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${token}`,
    },

  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}

resp = requests.get(
    f"https://api.huli.ai/authorize",
    headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class authorizeExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/authorize"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .method("GET", HttpRequest.BodyPublishers.noBody())
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func authorizeExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("GET", "https://api.huli.ai/authorize", nil)
    req.Header.Set("Authorization", "Bearer "+token)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

Consent Request Info

Returns the metadata the consent UI needs to render the prompt for an in-flight authorization request (client name, requested scopes, redirect_uri). Requires a session cookie established by the /authorize redirect.

GET/authorize/consent_info

Query Parameters

nametyperequireddescription
request_idstringrequiredConsent request identifier minted by `/authorize`.

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X GET https://api.huli.ai/authorize/consent_info \
  -H "Authorization: Bearer ${TOKEN}"
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/authorize/consent_info`,
  {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${token}`,
    },

  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}

resp = requests.get(
    f"https://api.huli.ai/authorize/consent_info",
    headers=headers,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class getConsentInfoExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/authorize/consent_info"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .method("GET", HttpRequest.BodyPublishers.noBody())
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func getConsentInfoExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("GET", "https://api.huli.ai/authorize/consent_info", nil)
    req.Header.Set("Authorization", "Bearer "+token)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

Records the operator's consent for an in-flight authorization request and returns the redirect URL (redirect_to) the frontend should navigate to. Requires the integrations.manage permission (Owner / Admin only).

POST/authorize/consent

Request Body

nametyperequireddescription
request_idstringrequired

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X POST https://api.huli.ai/authorize/consent \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/fhir+json" \
  -d @consent.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/authorize/consent`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/fhir+json",
    },
    body: JSON.stringify(payload),
  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}

resp = requests.post(
    f"https://api.huli.ai/authorize/consent",
    headers=headers,
    json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class grantConsentExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");
        String payload = "{}"; // your serialized FHIR resource

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/authorize/consent"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .header("Content-Type", "application/fhir+json")
        .method("POST", HttpRequest.BodyPublishers.ofString(payload))
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func grantConsentExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("POST", "https://api.huli.ai/authorize/consent", body)
    req.Header.Set("Authorization", "Bearer "+token)
    req.Header.Set("Content-Type", "application/fhir+json")
    // set req.Body to your serialized resource
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}

Records the operator's denial of an in-flight authorization request and returns the redirect URL with an OAuth error=access_denied parameter.

POST/authorize/deny

Request Body

nametyperequireddescription
request_idstringrequired

Code Samples

TOKEN=$(curl -s -X POST https://api.huli.ai/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=${CLIENT_ASSERTION}" \
  | jq -r .access_token)

curl -X POST https://api.huli.ai/authorize/deny \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/fhir+json" \
  -d @deny.json
const token = process.env.HULI_ACCESS_TOKEN ?? "";

const response = await fetch(
  `https://api.huli.ai/authorize/deny`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/fhir+json",
    },
    body: JSON.stringify(payload),
  }
);

if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
const data: unknown = await response.json();
import os
import requests

token = os.environ["HULI_ACCESS_TOKEN"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/fhir+json"}

resp = requests.post(
    f"https://api.huli.ai/authorize/deny",
    headers=headers,
    json=payload,
)
resp.raise_for_status()
print(resp.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class denyConsentExample {
    public static void main(String[] args) throws Exception {
        String token = System.getenv("HULI_ACCESS_TOKEN");
        String payload = "{}"; // your serialized FHIR resource

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.huli.ai/authorize/deny"))
        .header("Authorization", "Bearer " + token)
        .header("Accept", "application/fhir+json")
        .header("Content-Type", "application/fhir+json")
        .method("POST", HttpRequest.BodyPublishers.ofString(payload))
        .build();

        HttpResponse<String> response =
        client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("status: " + response.statusCode());
        System.out.println(response.body());
    }
}
import (
    "fmt"
    "net/http"
    "os"
)

func denyConsentExample() {
    token := os.Getenv("HULI_ACCESS_TOKEN")
    req, _ := http.NewRequest("POST", "https://api.huli.ai/authorize/deny", body)
    req.Header.Set("Authorization", "Bearer "+token)
    req.Header.Set("Content-Type", "application/fhir+json")
    // set req.Body to your serialized resource
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("status:", resp.Status)
}