Trinity Engine quickstart
Status:
stableCategory:referenceAdded:2026-04-21Last reviewed:2026-04-21
This page is the single place to learn how to call the Trinity Engine
from your language of choice. Every code block here was executed against
https://engine.tripod-health.com before publication. Per-endpoint docs
(e.g. /api/cdri/compute) reuse the
patterns shown here and focus on each endpoint's request body and
response shape.
If you only want to try one thing: pick your language below, copy the
full block, set ENGINE_API_KEY in your environment, and run it. The
block exercises GET /api/health/check, POST /api/debug/echo, and
the standard error envelope end-to-end.
Contents
- What you need
- Base URL, auth, and common conventions
- Pick your language — full pattern
- curl · Python · Node.js · TypeScript
- Go · Java · C# · PowerShell
- Additional languages — smoke call only
- Cross-language concerns
- Next steps
What you need
- An API key. Keys are issued per consumer; see authentication.md for how they are provisioned and rotated. If you do not have a key yet, contact your Tripod Health representative.
- HTTPS egress to
engine.tripod-health.com. Plain-HTTP connections tohttp://…are refused. - A modern HTTP client that speaks TLS 1.2+ and can set arbitrary request headers. Every language shown below ships a built-in client that works — no exotic dependencies required.
The examples below assume you have set ENGINE_API_KEY in your shell
environment, e.g.:
# bash / zsh
export ENGINE_API_KEY="your-api-key"
# PowerShell (session-only)
$env:ENGINE_API_KEY = "your-api-key"
Never hard-code keys into source files, and never log the raw key value.
Base URL, auth, and common conventions
| Concern | Value |
|---|---|
| Base URL | https://engine.tripod-health.com |
| Auth header | apikey: <your-key> (case-insensitive header name, literal value) |
| Request content type | application/json for any POST or PUT with a body |
| Response content type | application/json on every response, success or error |
| Optional correlation header | X-Request-ID on the way in; echoed as request_id in the response |
| Path prefix | Every callable endpoint lives under /api/ except the internal /health probe |
| Body size limit | 512 KB |
Every successful response body carries at least two fields:
request_id— a server-assigned (or client-supplied) correlation identifier. Quote this in support tickets.timestamp— ISO 8601 UTC, e.g."2026-04-21T22:17:14.033Z". Some endpoints emit this asengine_timestampinstead; per-endpoint docs call out which.
Every error response shares the envelope:
{
"timestamp": "2026-04-21T22:15:31.625Z",
"error": "DeIdentificationViolation",
"message": "note appears to contain a phone number",
"field": "note",
"request_id": "13858c9c-2737-46db-a673-052b984c942d"
}
The 401 Unauthorized body is the one exception — it is smaller
({ "message": "Unauthorized", "request_id": "..." }) and uses a
different envelope than the standard error shape. See
errors.md for the full error taxonomy.
Pick your language
Languages below are grouped by the depth of example they carry.
Primary languages get a complete scaffold covering the health
check, a POST with a JSON body, retry logic for 429 and 5xx, and
error-envelope parsing. These patterns are what per-endpoint docs
assume you already have.
Additional languages get a smoke call only. Adapt one of the primary patterns when you need retry and error handling.
curl (Unix shell)
The canonical debugging tool. Every per-endpoint doc shows at least one
curl example.
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="https://engine.tripod-health.com"
# 1. Health check.
curl -sS "$BASE_URL/api/health/check" \
-H "apikey: $ENGINE_API_KEY" \
-w "\nHTTP %{http_code}\n"
# 2. POST with a JSON body.
curl -sS -X POST "$BASE_URL/api/debug/echo" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Request-ID: quickstart-curl-$(date +%s)" \
-d '{"probe":"quickstart"}' \
-w "\nHTTP %{http_code}\n"
# 3. Trigger a de-id violation -- note the 400 envelope.
curl -sS -X POST "$BASE_URL/api/debug/echo" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"ring me on 555-123-4567"}' \
-w "\nHTTP %{http_code}\n"
For retry-on-429 / retry-on-5xx, call curl from a loop that parses the
status code and the Retry-After header — or use a tool like curl --retry combined with --retry-delay. curl treats 4xx as a success
by default; use -f (--fail) to make curl exit non-zero on >=400,
or the -w "\nHTTP %{http_code}\n" trick above to read the status in
shell.
Python (requests)
Healthcare analytics teams most commonly reach for requests. It
handles pooling, redirects, and gzip transparently.
"""Trinity Engine quickstart -- full Python pattern with error handling."""
import os
import time
import requests
BASE_URL = "https://engine.tripod-health.com"
session = requests.Session()
session.headers.update({
"apikey": os.environ["ENGINE_API_KEY"],
"Content-Type": "application/json",
})
class EngineError(RuntimeError):
"""Raised on any non-2xx the retry logic could not recover from."""
def __init__(self, status: int, envelope: dict):
self.status = status
self.envelope = envelope
super().__init__(
f"Engine {status} {envelope.get('error', 'Error')}: "
f"{envelope.get('message')} (request_id={envelope.get('request_id')})"
)
def call(method: str, path: str, json_body: dict | None = None, *, max_retries: int = 3) -> dict:
url = f"{BASE_URL}{path}"
for attempt in range(max_retries + 1):
resp = session.request(method, url, json=json_body, timeout=30)
if resp.status_code < 400:
return resp.json()
if resp.status_code == 429 and attempt < max_retries:
time.sleep(int(resp.headers.get("Retry-After", "1")))
continue
if 500 <= resp.status_code < 600 and attempt < max_retries:
time.sleep(2 ** attempt)
continue
try:
envelope = resp.json()
except ValueError:
envelope = {"message": resp.text}
raise EngineError(resp.status_code, envelope)
raise EngineError(0, {"message": f"retries exhausted for {method} {path}"})
if __name__ == "__main__":
print("health:", call("GET", "/api/health/check")["status"])
echoed = call("POST", "/api/debug/echo", {"probe": "quickstart"})
print("echoed body:", echoed["received"]["body"])
try:
call("POST", "/api/debug/echo", {"note": "ring me on 555-123-4567"})
except EngineError as e:
print("caught:", e)
Expected output:
health: ok
echoed body: {'probe': 'quickstart'}
caught: Engine 400 DeIdentificationViolation: note appears to contain a phone number (request_id=...)
If you prefer httpx (async) or the stdlib urllib (zero dependency),
both work — the structure above maps one-to-one.
Node.js (native fetch)
Requires Node 18+ for the built-in fetch. On older runtimes, swap in
undici.fetch or node-fetch.
// quickstart.mjs -- Trinity Engine quickstart, Node.js pattern.
const BASE_URL = "https://engine.tripod-health.com";
const API_KEY = process.env.ENGINE_API_KEY;
class EngineError extends Error {
constructor(status, envelope) {
super(`Engine ${status} ${envelope.error ?? "Error"}: ${envelope.message} (request_id=${envelope.request_id})`);
this.status = status;
this.envelope = envelope;
}
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function call(method, path, jsonBody, { maxRetries = 3 } = {}) {
const url = `${BASE_URL}${path}`;
const headers = { apikey: API_KEY, "Content-Type": "application/json" };
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const resp = await fetch(url, {
method,
headers,
body: jsonBody ? JSON.stringify(jsonBody) : undefined,
});
if (resp.ok) return resp.json();
if (resp.status === 429 && attempt < maxRetries) {
const delay = parseInt(resp.headers.get("retry-after") ?? "1", 10);
await sleep(delay * 1000);
continue;
}
if (resp.status >= 500 && resp.status < 600 && attempt < maxRetries) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
let envelope;
try { envelope = await resp.json(); } catch { envelope = { message: await resp.text() }; }
throw new EngineError(resp.status, envelope);
}
throw new EngineError(0, { message: `retries exhausted for ${method} ${path}` });
}
console.log("health:", (await call("GET", "/api/health/check")).status);
const echoed = await call("POST", "/api/debug/echo", { probe: "quickstart" });
console.log("echoed body:", JSON.stringify(echoed.received.body));
try {
await call("POST", "/api/debug/echo", { note: "ring me on 555-123-4567" });
} catch (e) {
console.log("caught:", e.message);
}
Run with node quickstart.mjs. Expected output mirrors the Python
example above.
TypeScript
Same runtime as Node.js; the difference is compile-time type safety. Generate a typed client by pairing this scaffold with the response shapes documented in each per-endpoint doc.
// quickstart.ts -- typed Trinity Engine client scaffold.
interface EngineErrorEnvelope {
error?: string;
message: string;
timestamp?: string;
request_id?: string;
field?: string;
[k: string]: unknown;
}
export class EngineError extends Error {
constructor(
readonly status: number,
readonly envelope: EngineErrorEnvelope,
) {
super(
`Engine ${status} ${envelope.error ?? "Error"}: ${envelope.message} ` +
`(request_id=${envelope.request_id})`,
);
}
}
export interface CallOptions { maxRetries?: number }
const BASE_URL = "https://engine.tripod-health.com";
export async function call<T = unknown>(
method: string,
path: string,
jsonBody?: unknown,
{ maxRetries = 3 }: CallOptions = {},
): Promise<T> {
const url = `${BASE_URL}${path}`;
const headers = {
apikey: process.env.ENGINE_API_KEY!,
"Content-Type": "application/json",
};
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const resp = await fetch(url, {
method,
headers,
body: jsonBody ? JSON.stringify(jsonBody) : undefined,
});
if (resp.ok) return (await resp.json()) as T;
if (resp.status === 429 && attempt < maxRetries) {
const delay = parseInt(resp.headers.get("retry-after") ?? "1", 10);
await new Promise((r) => setTimeout(r, delay * 1000));
continue;
}
if (resp.status >= 500 && attempt < maxRetries) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
let envelope: EngineErrorEnvelope;
try { envelope = (await resp.json()) as EngineErrorEnvelope; }
catch { envelope = { message: await resp.text() }; }
throw new EngineError(resp.status, envelope);
}
throw new EngineError(0, { message: `retries exhausted for ${method} ${path}` });
}
// Example typed usage:
interface HealthResponse { status: "ok" | "degraded"; db: string; request_id: string }
const health = await call<HealthResponse>("GET", "/api/health/check");
console.log("health:", health.status);
Compile with tsc --target es2022 --module esnext --moduleResolution bundler --strict --skipLibCheck --lib es2022,dom, or run directly via
tsx quickstart.ts / ts-node.
Go (net/http)
Zero third-party dependencies. Go's stdlib is the common choice for hospital analytics pipelines that need small, static binaries.
// quickstart.go -- Trinity Engine quickstart, Go pattern.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"os"
"strconv"
"time"
)
const baseURL = "https://engine.tripod-health.com"
type EngineError struct {
Status int
Code string
Message string
RequestID string
Field string
}
func (e *EngineError) Error() string {
return fmt.Sprintf("Engine %d %s: %s (request_id=%s)",
e.Status, e.Code, e.Message, e.RequestID)
}
func call(method, path string, body any, maxRetries int) (map[string]any, error) {
client := &http.Client{Timeout: 30 * time.Second}
var payload []byte
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
payload = b
}
for attempt := 0; attempt <= maxRetries; attempt++ {
req, err := http.NewRequest(method, baseURL+path, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("apikey", os.Getenv("ENGINE_API_KEY"))
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
respBody, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode < 400 {
var out map[string]any
if err := json.Unmarshal(respBody, &out); err != nil {
return nil, err
}
return out, nil
}
if resp.StatusCode == 429 && attempt < maxRetries {
delay, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if delay == 0 {
delay = 1
}
time.Sleep(time.Duration(delay) * time.Second)
continue
}
if resp.StatusCode >= 500 && resp.StatusCode < 600 && attempt < maxRetries {
time.Sleep(time.Duration(math.Pow(2, float64(attempt))) * time.Second)
continue
}
var env map[string]any
_ = json.Unmarshal(respBody, &env)
code, _ := env["error"].(string)
msg, _ := env["message"].(string)
rid, _ := env["request_id"].(string)
field, _ := env["field"].(string)
return nil, &EngineError{
Status: resp.StatusCode, Code: code, Message: msg,
RequestID: rid, Field: field,
}
}
return nil, fmt.Errorf("retries exhausted for %s %s", method, path)
}
func main() {
health, err := call("GET", "/api/health/check", nil, 3)
if err != nil {
panic(err)
}
fmt.Println("health:", health["status"])
echoed, err := call("POST", "/api/debug/echo",
map[string]string{"probe": "quickstart"}, 3)
if err != nil {
panic(err)
}
received := echoed["received"].(map[string]any)
fmt.Println("echoed body:", received["body"])
if _, err := call("POST", "/api/debug/echo",
map[string]string{"note": "ring me on 555-123-4567"}, 3); err != nil {
fmt.Println("caught:", err)
}
}
Build with go run quickstart.go (no go.mod needed — the example uses
stdlib only).
Java (java.net.http.HttpClient)
JDK 11+ ships a modern HTTP client that replaces the older
HttpURLConnection. Healthcare EMR integrations on Java 11+ should use
it directly; on Java 8, fall back to Apache HttpClient or OkHttp.
// JavaQuickstart.java -- compile with `javac` or run as a single source file
// with `java JavaQuickstart.java` on JDK 11+.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
public class JavaQuickstart {
static final String BASE_URL = "https://engine.tripod-health.com";
static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10)).build();
public static class EngineException extends RuntimeException {
public final int status;
public final String code;
public final String requestId;
public EngineException(int status, String code, String message, String requestId) {
super("Engine " + status + " " + code + ": " + message
+ " (request_id=" + requestId + ")");
this.status = status; this.code = code; this.requestId = requestId;
}
}
static HttpResponse<String> send(String method, String path, String jsonBody) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.timeout(Duration.ofSeconds(30))
.header("apikey", System.getenv("ENGINE_API_KEY"));
if (jsonBody == null) {
b.method(method, BodyPublishers.noBody());
} else {
b.method(method, BodyPublishers.ofString(jsonBody));
b.header("Content-Type", "application/json");
}
return CLIENT.send(b.build(), BodyHandlers.ofString());
}
static String call(String method, String path, String jsonBody, int maxRetries) throws Exception {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
HttpResponse<String> resp = send(method, path, jsonBody);
int s = resp.statusCode();
if (s < 400) return resp.body();
if (s == 429 && attempt < maxRetries) {
int delay = resp.headers().firstValue("Retry-After")
.map(Integer::parseInt).orElse(1);
Thread.sleep(delay * 1000L);
continue;
}
if (s >= 500 && s < 600 && attempt < maxRetries) {
Thread.sleep((long) Math.pow(2, attempt) * 1000L);
continue;
}
String body = resp.body();
String code = extract(body, "error");
String msg = extract(body, "message");
String rid = extract(body, "request_id");
throw new EngineException(s, code == null ? "Error" : code, msg, rid);
}
throw new RuntimeException("retries exhausted");
}
// Minimal JSON extractor for the error envelope only.
// For request/response bodies, use Jackson, Gson, or your JSON library of choice.
static String extract(String json, String key) {
String needle = "\"" + key + "\":\"";
int i = json.indexOf(needle);
if (i < 0) return null;
int start = i + needle.length();
int end = json.indexOf('"', start);
return end < 0 ? null : json.substring(start, end);
}
public static void main(String[] args) throws Exception {
String health = call("GET", "/api/health/check", null, 3);
System.out.println("health: " + extract(health, "status"));
String echoed = call("POST", "/api/debug/echo",
"{\"probe\":\"quickstart\"}", 3);
System.out.println("echoed request_id: " + extract(echoed, "request_id"));
try {
call("POST", "/api/debug/echo",
"{\"note\":\"ring me on 555-123-4567\"}", 3);
} catch (EngineException e) {
System.out.println("caught: " + e.getMessage());
}
}
}
The minimal extract helper above is deliberately primitive — it only
handles string fields of the error envelope. For real request and
response bodies, reach for a JSON library:
- Jackson —
jackson-databindwith@JsonPropertyannotations - Gson — reflection-based, zero setup
jakarta.json(JSR 374) — stdlib-adjacent, ships with Jakarta EE
C# (.NET HttpClient)
Requires .NET 8+ for top-level statements and the JsonContent helper.
On older .NET (Framework 4.x, Core 3.x), the pattern is identical but
you must wrap the code in a static void Main and construct
StringContent manually.
// Quickstart.cs -- single-file .NET 8+ program.
// dotnet new console -o quickstart && cp Quickstart.cs quickstart/Program.cs && dotnet run --project quickstart
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
const string BaseUrl = "https://engine.tripod-health.com";
using var client = new HttpClient
{
BaseAddress = new Uri(BaseUrl),
Timeout = TimeSpan.FromSeconds(30),
};
client.DefaultRequestHeaders.Add("apikey", Environment.GetEnvironmentVariable("ENGINE_API_KEY"));
async Task<JsonElement> Call(HttpMethod method, string path, object? jsonBody = null, int maxRetries = 3)
{
for (var attempt = 0; attempt <= maxRetries; attempt++)
{
using var req = new HttpRequestMessage(method, path);
if (jsonBody is not null)
req.Content = JsonContent.Create(jsonBody);
using var resp = await client.SendAsync(req);
if (resp.IsSuccessStatusCode)
return await resp.Content.ReadFromJsonAsync<JsonElement>();
if (resp.StatusCode == (HttpStatusCode)429 && attempt < maxRetries)
{
var delay = resp.Headers.TryGetValues("Retry-After", out var vs)
&& int.TryParse(vs.First(), out var d) ? d : 1;
await Task.Delay(TimeSpan.FromSeconds(delay));
continue;
}
if ((int)resp.StatusCode >= 500 && attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
continue;
}
var envelope = await resp.Content.ReadFromJsonAsync<JsonElement>();
var code = envelope.TryGetProperty("error", out var e) ? e.GetString() : "Error";
var msg = envelope.TryGetProperty("message", out var m) ? m.GetString() : resp.ReasonPhrase;
var rid = envelope.TryGetProperty("request_id", out var r) ? r.GetString() : null;
throw new HttpRequestException(
$"Engine {(int)resp.StatusCode} {code}: {msg} (request_id={rid})");
}
throw new Exception($"Retries exhausted for {method} {path}");
}
var health = await Call(HttpMethod.Get, "/api/health/check");
Console.WriteLine($"health: {health.GetProperty("status").GetString()}");
var echoed = await Call(HttpMethod.Post, "/api/debug/echo", new { probe = "quickstart" });
Console.WriteLine($"echoed body: {echoed.GetProperty("received").GetProperty("body")}");
try
{
await Call(HttpMethod.Post, "/api/debug/echo",
new { note = "ring me on 555-123-4567" });
}
catch (HttpRequestException ex)
{
Console.WriteLine($"caught: {ex.Message}");
}
HttpClient is designed to be long-lived — share one per process, not
one per call. In ASP.NET Core, prefer IHttpClientFactory over direct
construction so sockets and connection pools are reused.
PowerShell
Works on Windows PowerShell 5.1 (ships with Windows 10/11) and
PowerShell 7+. The 5.1 path uses $_.ErrorDetails.Message to read the
error body because Invoke-RestMethod throws on non-2xx and does not
return the response body directly.
# quickstart.ps1 -- Trinity Engine quickstart, PowerShell 5.1 / 7+.
$BaseUrl = "https://engine.tripod-health.com"
$Headers = @{ apikey = $env:ENGINE_API_KEY }
function Invoke-Engine {
param(
[Parameter(Mandatory)][string]$Method,
[Parameter(Mandatory)][string]$Path,
$Body,
[int]$MaxRetries = 3
)
$url = "$BaseUrl$Path"
for ($attempt = 0; $attempt -le $MaxRetries; $attempt++) {
try {
$params = @{
Uri = $url
Method = $Method
Headers = $Headers
TimeoutSec = 30
ErrorAction = "Stop"
}
if ($null -ne $Body) {
$params.Body = ($Body | ConvertTo-Json -Depth 10 -Compress)
$params.ContentType = "application/json"
}
return Invoke-RestMethod @params
} catch {
$resp = $_.Exception.Response
$status = if ($resp) { [int]$resp.StatusCode } else { 0 }
if ($status -eq 429 -and $attempt -lt $MaxRetries) {
$delay = 1
try { if ($resp.Headers["Retry-After"]) { $delay = [int]$resp.Headers["Retry-After"] } } catch {}
Start-Sleep -Seconds $delay; continue
}
if ($status -ge 500 -and $status -lt 600 -and $attempt -lt $MaxRetries) {
Start-Sleep -Seconds ([Math]::Pow(2, $attempt)); continue
}
$envText = $_.ErrorDetails.Message
$envelope = $null
if ($envText) { try { $envelope = $envText | ConvertFrom-Json } catch {} }
$code = if ($envelope -and $envelope.error) { $envelope.error } else { "Error" }
$msg = if ($envelope -and $envelope.message) { $envelope.message } else { $envText }
$rid = if ($envelope -and $envelope.request_id) { $envelope.request_id } else { "" }
throw ("Engine {0} {1}: {2} (request_id={3})" -f $status, $code, $msg, $rid)
}
}
throw "retries exhausted"
}
$health = Invoke-Engine -Method Get -Path "/api/health/check"
"health: $($health.status)"
$echoed = Invoke-Engine -Method Post -Path "/api/debug/echo" -Body @{ probe = "quickstart" }
"echoed body: $($echoed.received.body | ConvertTo-Json -Compress)"
try {
Invoke-Engine -Method Post -Path "/api/debug/echo" -Body @{ note = "ring me on 555-123-4567" }
} catch {
"caught: $($_.Exception.Message)"
}
Run with powershell.exe -ExecutionPolicy Bypass -File quickstart.ps1
on Windows PowerShell 5.1, or pwsh -File quickstart.ps1 on
PowerShell 7+. On 5.1, body serialization uses ConvertTo-Json -Depth 10 to avoid the default depth-2 truncation.
Additional languages
The following languages get a smoke call only. Adapt the retry, backoff, and error-envelope logic from one of the primary patterns above when you are ready for production.
Ruby (Net::HTTP)
Uses Ruby's stdlib only — works on any Ruby 2.7+.
# quickstart.rb
require "net/http"
require "json"
require "uri"
uri = URI("https://engine.tripod-health.com/api/health/check")
req = Net::HTTP::Get.new(uri)
req["apikey"] = ENV["ENGINE_API_KEY"]
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
res = http.request(req)
body = JSON.parse(res.body)
raise "Engine #{res.code}: #{body["message"]}" unless res.is_a?(Net::HTTPSuccess)
puts "health: #{body["status"]} db: #{body["db"]}"
end
For richer behavior — connection pooling, retries, multipart — use
faraday or httpx.
PHP (cURL extension)
Uses PHP's built-in curl_* functions — no Composer dependencies.
<?php
// quickstart.php -- run with `php quickstart.php`. Requires PHP 8.0+.
$ch = curl_init("https://engine.tripod-health.com/api/health/check");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["apikey: " . getenv("ENGINE_API_KEY")],
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
$env = json_decode($body, true) ?: ["message" => $body];
fwrite(STDERR, "Engine {$status} {$env['error']}: {$env['message']}\n");
exit(1);
}
$data = json_decode($body, true);
echo "health: {$data['status']} db: {$data['db']}\n";
curl_close() is a no-op on PHP 8.0+ and is deprecated in 8.5 — omit
it. For heavier integrations, reach for Guzzle (guzzlehttp/guzzle).
Rust (reqwest)
Pulls in two crates. Use the rustls TLS backend to avoid an OpenSSL system dependency.
# Cargo.toml
[package]
name = "engine_smoke"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde_json = "1"
// src/main.rs
use serde_json::Value;
use std::env;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("ENGINE_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let resp = client
.get("https://engine.tripod-health.com/api/health/check")
.header("apikey", api_key)
.send()?;
let status = resp.status().as_u16();
let body: Value = resp.json()?;
println!("rust http={} status={} db={}", status, body["status"], body["db"]);
Ok(())
}
For async, swap reqwest::blocking for the top-level reqwest client
with tokio.
wget (shell)
For simple CI shell scripts that do not want another dependency beyond
the wget binary.
wget -qO- "https://engine.tripod-health.com/api/health/check" \
--header="apikey: $ENGINE_API_KEY"
wget does not pretty-print JSON or give easy access to the HTTP
status code — if your CI step needs to branch on the status or parse
the response body, use curl instead.
Cross-language concerns
The per-language scaffolds above bake in the right defaults, but if you are building your own from scratch, these are the conventions every caller should follow.
Correlation IDs
- On the way in, supply an
X-Request-IDheader with any string that identifies this call inside your system (up to 128 characters). If you do not, one is generated for you. - On the way out, read
request_idfrom the response body. It matches theX-Request-IDresponse header. - Quote
request_idin support tickets. It is Trinity's primary lookup key for troubleshooting.
De-identification
Trinity never sees identifiable data. Every request body is scanned
before any business logic runs; identifiers trigger 400 DeIdentificationViolation. Do not send:
- Names (
name,first_name,last_name,full_name) - Dates of birth (
dob,date_of_birth) — useage_bracketinstead - SSNs or SSN-shaped values (
\d{3}-\d{2}-\d{4}) - Emails, phone numbers, MRNs, patient IDs, member IDs
See conventions.md for the full list and the structural descriptors Trinity accepts instead (age bracket, occupation class, ICD category, etc.).
Rate limits and Retry-After
On a 429, respect the Retry-After header (seconds). The primary
scaffolds above do this automatically. See
rate-limits.md for the full header set.
Do not immediately retry on 429 — that just burns your quota
faster. Exponential backoff on 429 without reading Retry-After is
also wrong; the response tells you exactly how long to wait.
Timeouts and retries
- Connect timeout: 10 seconds is generous for healthy prod.
- Request timeout: 30 seconds covers every synchronous endpoint,
including
/api/cdri/compute. If you see requests taking longer, open a ticket — do not silently raise your timeout. - Retries: retry
429(respectingRetry-After) and5xx(exponential backoff). Do not retry4xxother than429— those are client errors and retrying will not change the outcome. - Idempotency:
GETcalls are idempotent.POSTcalls are computationally repeatable but not idempotent at the storage layer; each call is a new request server-side. Retry on genuine failures, not speculatively.
TLS and certificate validation
- Always validate the TLS certificate. Never disable certificate checking "to make it work" — Trinity uses a standard public certificate that works out of the box in every modern HTTPS client.
- Minimum TLS version: 1.2. TLS 1.0 and 1.1 are rejected.
- If you are behind a corporate proxy that does TLS inspection, install your proxy's CA certificate into your client's trust store. Trinity does not support pinned certificates or custom TLS configuration per consumer.
Next steps
Once the smoke calls run, walk through the per-endpoint docs. Start with:
- Infrastructure ·
/api/health/check— deeper health telemetry than the quickstart smoke. - Catalog ·
/api/catalog/get— discover every instrument, resource, and operation the API exposes. - Debug ·
/api/debug/echo— a worked example of the de-identification contract in isolation. - Pattern ·
/score— how any single-instrument scoring call works. - Composite ·
/api/cdri/compute— the flagship composite endpoint.
Every per-endpoint doc assumes the scaffolding on this page. If a per-endpoint example elides a retry loop or error handler, that's why — it is delegating to the quickstart, not telling you those concerns do not matter.
Change history
| Date | Change |
|---|---|
| 2026-04-21 | Initial publication. |