Loading documentation

Access required

Your account doesn't have access to the documentation. Contact your administrator.

Sign out
Skip to main content

Trinity Engine quickstart

Status: stable Category: reference Added: 2026-04-21 Last 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

  1. 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.
  2. HTTPS egress to engine.tripod-health.com. Plain-HTTP connections to http://… are refused.
  3. 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

ConcernValue
Base URLhttps://engine.tripod-health.com
Auth headerapikey: <your-key> (case-insensitive header name, literal value)
Request content typeapplication/json for any POST or PUT with a body
Response content typeapplication/json on every response, success or error
Optional correlation headerX-Request-ID on the way in; echoed as request_id in the response
Path prefixEvery callable endpoint lives under /api/ except the internal /health probe
Body size limit512 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 as engine_timestamp instead; 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:

  • Jacksonjackson-databind with @JsonProperty annotations
  • 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-ID header 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_id from the response body. It matches the X-Request-ID response header.
  • Quote request_id in 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) — use age_bracket instead
  • 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 (respecting Retry-After) and 5xx (exponential backoff). Do not retry 4xx other than 429 — those are client errors and retrying will not change the outcome.
  • Idempotency: GET calls are idempotent. POST calls 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:

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

DateChange
2026-04-21Initial publication.