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:
issandsub: yourclient_id(API key prefix)aud: this token endpoint URLexp: expiration (max 5 minutes from now)iat: issued-at timestampjti: 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.
/auth/tokenRequest Body
| name | type | required | description |
|---|---|---|---|
| grant_type | string | required | Must be `client_credentials`. |
| client_assertion_type | string | required | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. |
| client_assertion | string | required | RSA-signed JWT (JWS, RS384) containing the required claims. |
| scope | string | optional | Space-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
| Code | Status | Description |
|---|---|---|
| HPB-00101 | 400 | Validation error |
| HPB-00106 | 401 | Authentication failed |
| HPB-00105 | 429 | Rate 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.
/auth/introspectResponse — 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
| Code | Status | Description |
|---|---|---|
| HPB-00106 | 401 | Authentication failed |
| HPB-00105 | 429 | Rate 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.
/auth/revokeRequest Body
| name | type | required | description |
|---|---|---|---|
| token | string | required | The access or refresh token to revoke. |
| token_type_hint | string | optional | Optional 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.
/authorizeQuery Parameters
| name | type | required | description |
|---|---|---|---|
| response_type | string | required | |
| client_id | string | required | |
| redirect_uri | string | required | |
| scope | string | required | |
| state | string | required | |
| code_challenge | string | required | |
| code_challenge_method | string | required |
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.
/authorize/consent_infoQuery Parameters
| name | type | required | description |
|---|---|---|---|
| request_id | string | required | Consent 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)
}
Grant Consent
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).
/authorize/consentRequest Body
| name | type | required | description |
|---|---|---|---|
| request_id | string | required |
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)
}
Deny Consent
Records the operator's denial of an in-flight authorization request and returns the redirect URL with an OAuth error=access_denied parameter.
/authorize/denyRequest Body
| name | type | required | description |
|---|---|---|---|
| request_id | string | required |
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)
}