Skip to content

C / C++ SDK

The C/C++ SDK wraps the Rust SDK via a C ABI and ships as libratify_c.a (static) and libratify_c.so / libratify_c.dylib (shared). The header (include/ratify.h) is auto-generated by cbindgen and includes extern "C" guards so it works in both C and C++.

Byte-for-byte interoperable with Go, TypeScript, Python, and Rust. Apache-2.0.

Stability: every primitive on this page is stable in 1.0.0-alpha.13.

You’re writingUse
A Go service or CLIGo SDK
A Node.js or browser appTypeScript SDK
A Python script or ML pipelinePython SDK
A Rust service or high-performance binaryRust SDK — use directly, no FFI overhead
C or C++ codeThis SDK
Firmware, RTOS, hardware driverThis SDK (static library, libratify_c.a)
A language that FFIs to C (Swift, Zig, Julia, Lua, etc.)This SDK

Option 1 — Pre-built library (no Rust required)

Section titled “Option 1 — Pre-built library (no Rust required)”

Download the pre-built library for your target from the GitHub Releases page. Each release ships .tar.gz archives for common targets:

ArchiveTarget
ratify-c-*-x86_64-linux.tar.gzx86-64 Linux (server, desktop)
ratify-c-*-aarch64-linux.tar.gzARM64 Linux (Raspberry Pi 4, embedded SBCs)
ratify-c-*-armv7-linux.tar.gzARM32 Linux (Raspberry Pi 2/3)
ratify-c-*-x86_64-macos.tar.gzmacOS Intel
ratify-c-*-aarch64-macos.tar.gzmacOS Apple Silicon
ratify-c-*-x86_64-windows.zipWindows x86-64

Each archive contains lib/libratify_c.a, lib/libratify_c.so (or .dylib/.dll), and include/ratify.h. The header is also committed to the repo at sdks/c/include/ratify.h — you can vendor it directly without downloading a release.

Option 2 — Build from source (requires Rust 1.70+)

Section titled “Option 2 — Build from source (requires Rust 1.70+)”

If your target isn’t in the release archives, or you need a custom feature flag:

Terminal window
git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol/sdks/c
cargo build --release

Outputs:

target/release/libratify_c.a — static library
target/release/libratify_c.so — shared library (Linux)
target/release/libratify_c.dylib — shared library (macOS)
include/ratify.h — C/C++ header

Cross-compile for embedded targets with cross:

Terminal window
cargo install cross --git https://github.com/cross-rs/cross
# ARM64 — Raspberry Pi 4, embedded Linux
cross build --release --target aarch64-unknown-linux-gnu
# ARM32 — Raspberry Pi 2/3
cross build --release --target armv7-unknown-linux-gnueabihf
# ARM Cortex-M4/M7 — FreeRTOS / Zephyr
rustup target add thumbv7em-none-eabihf
cargo build --release --target thumbv7em-none-eabihf --features custom-entropy
# RISC-V 64
cross build --release --target riscv64gc-unknown-linux-gnu

Quick start — Delegate → Present → Verify

Section titled “Quick start — Delegate → Present → Verify”

The complete agent authorization flow in C. All API entry/exit conditions are explicitly handled.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "ratify.h"
static int fail(const char *step, char *err) {
fprintf(stderr, "FAIL: %s%s\n", step, err ? err : "unknown error");
ratify_error_free(err);
return 1;
}
int main(void) {
printf("Ratify %s — C SDK example\n\n", ratify_version());
char *err = NULL;
/* 1. Generate identities */
RatifyHumanRoot *root = NULL;
if (ratify_human_root_generate(&root) != RatifyOk)
return fail("ratify_human_root_generate", NULL);
RatifyAgent *agent = NULL;
if (ratify_agent_generate("MyDroneBot", "drone", &agent) != RatifyOk) {
ratify_human_root_free(root);
return fail("ratify_agent_generate", NULL);
}
/* 2. Issue a DelegationCert */
int64_t now = (int64_t)time(NULL);
RatifyDelegationCert *cert = NULL;
if (ratify_delegation_issue(root, agent,
"[\"physical:enter\"]",
now, now + 3600LL,
&cert, &err) != RatifyOk)
return fail("ratify_delegation_issue", err);
char *cert_json = ratify_delegation_cert_to_json(cert, &err);
if (!cert_json)
return fail("ratify_delegation_cert_to_json", err);
/* 3. Generate challenge and build ProofBundle */
uint8_t challenge[32];
if (ratify_challenge_generate(challenge, 32) != RatifyOk)
return fail("ratify_challenge_generate", NULL);
RatifyProofBundle *bundle = NULL;
if (ratify_proof_bundle_create(agent, cert_json,
challenge, 32,
now,
&bundle, &err) != RatifyOk)
return fail("ratify_proof_bundle_create", err);
ratify_string_free(cert_json);
char *bundle_json = ratify_proof_bundle_to_json(bundle, &err);
if (!bundle_json)
return fail("ratify_proof_bundle_to_json", err);
/* 4. Verify */
RatifyVerifyResult *result = NULL;
ratify_verify_bundle(bundle_json, "physical:enter", now, &result, &err);
if (ratify_verify_result_is_valid(result)) {
char *agent_id = ratify_verify_result_agent_id(result);
printf("authorized agent: %s\n", agent_id);
ratify_string_free(agent_id);
} else {
char *status = ratify_verify_result_identity_status(result);
printf("rejected: %s\n", status);
ratify_string_free(status);
}
/* 5. Cleanup */
ratify_verify_result_free(result);
ratify_string_free(bundle_json);
ratify_proof_bundle_free(bundle);
ratify_delegation_cert_free(cert);
ratify_agent_free(agent);
ratify_human_root_free(root);
return 0;
}

Build and run:

Terminal window
# macOS
cc example.c -I include -L target/release \
-lratify_c -lpthread -framework Security -framework CoreFoundation \
-o example && ./example
# Linux
cc example.c -I include -L target/release \
-lratify_c -lpthread -ldl -lm -o example && ./example

Verifier-side: branching on identity status

Section titled “Verifier-side: branching on identity status”

ratify_verify_result_identity_status returns a stable string constant you can compare directly. These values are byte-identical across every Ratify SDK.

char *status = ratify_verify_result_identity_status(result);
if (strcmp(status, "authorized_agent") == 0) {
/* ✓ All checks passed.
Use ratify_verify_result_agent_id / ratify_verify_result_human_id
to log the delegation chain. */
} else if (strcmp(status, "expired") == 0) {
/* ✗ At least one cert in the chain is past its expires_at. */
} else if (strcmp(status, "revoked") == 0) {
/* ✗ A cert ID matched a revoked entry from your revocation callback. */
} else if (strcmp(status, "scope_denied") == 0) {
/* ✗ The required scope is not in the chain's effective scope. */
} else if (strcmp(status, "constraint_denied") == 0) {
/* ✗ A geo / time / amount / rate constraint was violated. */
} else if (strcmp(status, "constraint_unverifiable") == 0) {
/* ✗ A constraint present on a cert had no context to evaluate
(e.g. geo_circle but has_location == 0). Fail-closed. */
} else if (strcmp(status, "constraint_unknown") == 0) {
/* ✗ Unknown constraint type — future-proof fail-closed behavior. */
} else if (strcmp(status, "delegation_not_authorized") == 0) {
/* ✗ A cert in the chain sub-delegated without identity:delegate. */
} else {
/* "invalid" — bad signature, broken chain, stale challenge, etc.
ratify_verify_result_error_reason() carries a stable error code. */
char *reason = ratify_verify_result_error_reason(result);
fprintf(stderr, "invalid: %s\n", reason);
ratify_string_free(reason);
}
ratify_string_free(status);
ratify_verify_result_free(result);

The C API works entirely in JSON strings. ratify_proof_bundle_to_json serializes a bundle; ratify_verify_bundle accepts the JSON string directly. This is the natural transport layer:

/* Agent side: serialize bundle to JSON, send over your transport.
The JSON string is what you PUT in an HTTP header, publish to MQTT,
write to a CAN-bus frame, or pass over any other channel. */
char *bundle_json = ratify_proof_bundle_to_json(bundle, &err);
/* send bundle_json to the verifier ... */
/* Verifier side: receive the JSON string, verify inline — no
deserialization step needed. */
RatifyVerifyResult *result = NULL;
ratify_verify_bundle(bundle_json, "physical:enter",
(int64_t)time(NULL), &result, &err);

Similarly, certs are serialized with ratify_delegation_cert_to_json for storage or forwarding, and the verifier accepts them as-is.

Supply a RatifyVerifierContext when a delegation may carry constraints. Use ratify_verify_bundle_opts instead of the simple ratify_verify_bundle:

/* Gate: drone must be within a pre-authorized geo circle */
RatifyVerifierContext ctx = {0};
ctx.current_lat = 37.7749;
ctx.current_lon = -122.4194;
ctx.has_location = 1; /* MUST set this; lat/lon are ignored if 0 */
RatifyVerifyOptions opts = {0};
opts.required_scope = "drone:deliver";
opts.context = &ctx;
RatifyVerifyResult *result = NULL;
char *err = NULL;
ratify_verify_bundle_opts(bundle_json, &opts, &result, &err);
/* result is fail-closed on constraint violation:
identity_status = "constraint_denied" if outside radius
identity_status = "constraint_unverifiable" if has_location == 0 */

Other context fields:

ctx.current_speed_mps = 12.5; ctx.has_speed = 1; /* max_speed_mps constraint */
ctx.current_amount = 99.0; ctx.has_amount = 1; /* max_amount constraint */
ctx.current_rate = 3.0; ctx.has_rate = 1; /* max_rate constraint */

Agent-to-agent delegation uses the same primitives. Alice grants Agent A the identity:delegate scope; A can then issue a delegation to Agent B. Without identity:delegate, any sub-delegation attempt is rejected with delegation_not_authorized.

/* ── Alice → Agent A ─────────────────────────────────────────────────── */
RatifyDelegationCert *cert_a = NULL;
/* "[\"meeting:attend\",\"identity:delegate\"]" grants sub-delegation right */
ratify_delegation_issue(root, agent_a,
"[\"meeting:attend\",\"identity:delegate\"]",
now, now + 86400LL, &cert_a, &err);
char *cert_a_json = ratify_delegation_cert_to_json(cert_a, &err);
/* ── Agent A → Agent B (subset of A's grant) ─────────────────────────── */
/* Agent A re-wraps itself as a "root" for B's cert: use ratify_issue_sub_delegation.
B's cert scope must be a subset; expiry must be ≤ A's cert expiry. */
RatifyDelegationCert *cert_b = NULL;
ratify_sub_delegation_issue(agent_a, agent_b,
"[\"meeting:attend\"]", /* no identity:delegate — B cannot sub-delegate further */
now, now + 3600LL, cert_a_json, &cert_b, &err);
char *cert_b_json = ratify_delegation_cert_to_json(cert_b, &err);
/* ── Build and verify the two-hop chain ──────────────────────────────── */
/* Pass both cert JSONs as a JSON array: "[cert_b_json, cert_a_json]"
Order: leaf first (cert closest to agent), root last. */
char chain[8192];
snprintf(chain, sizeof(chain), "[%s,%s]", cert_b_json, cert_a_json);
RatifyProofBundle *bundle = NULL;
ratify_proof_bundle_create_chain(agent_b, chain, challenge, 32,
now, &bundle, &err);
char *bundle_json = ratify_proof_bundle_to_json(bundle, &err);
RatifyVerifyResult *result = NULL;
ratify_verify_bundle(bundle_json, "meeting:attend", now, &result, &err);
/* result.human_id is Alice; result.agent_id is Agent B */

Pass a revocation callback in RatifyVerifyOptions. The callback receives the cert_id string for each cert in the chain and must return 1 (revoked), 0 (not revoked), or -1 (lookup failed — fail-closed, same as revoked).

typedef struct { const char **revoked_ids; size_t count; } RevocationDB;
static int is_revoked(const char *cert_id, void *userdata) {
RevocationDB *db = (RevocationDB *)userdata;
for (size_t i = 0; i < db->count; i++) {
if (strcmp(cert_id, db->revoked_ids[i]) == 0)
return 1; /* revoked */
}
return 0; /* not revoked */
/* return -1 on lookup error to fail-closed */
}
/* Wire the callback */
RevocationDB my_db = { .revoked_ids = revoked, .count = n_revoked };
RatifyVerifyOptions opts = {0};
opts.required_scope = "physical:enter";
opts.revocation_fn = is_revoked;
opts.revocation_userdata = &my_db;
RatifyVerifyResult *result = NULL;
char *err = NULL;
ratify_verify_bundle_opts(bundle_json, &opts, &result, &err);
/* identity_status == "revoked" if any cert_id is in the revoked list */
cmake_minimum_required(VERSION 3.20)
project(my_agent C)
set(RATIFY_SDK_DIR "${CMAKE_SOURCE_DIR}/vendor/ratify-c")
add_library(ratify STATIC IMPORTED)
set_target_properties(ratify PROPERTIES
IMPORTED_LOCATION "${RATIFY_SDK_DIR}/lib/libratify_c.a"
INTERFACE_INCLUDE_DIRECTORIES "${RATIFY_SDK_DIR}/include"
)
add_executable(my_agent main.c)
target_link_libraries(my_agent PRIVATE ratify pthread dl m)

Embedded / RTOS — FreeRTOS custom entropy

Section titled “Embedded / RTOS — FreeRTOS custom entropy”

On standard OS targets (Linux, macOS, Raspberry Pi) entropy is automatic. On RTOS targets without /dev/urandom, enable the custom-entropy Cargo feature and register your hardware TRNG:

Cargo.toml:

ratify-c = { path = "…", features = ["custom-entropy"] }

Application startup (STM32 example):

#include "ratify.h"
static int my_entropy(uint8_t *buf, size_t len) {
for (size_t i = 0; i < len; i += 4) {
uint32_t rnd;
if (HAL_RNG_GenerateRandomNumber(&hrng, &rnd) != HAL_OK)
return -1; /* -1 = entropy unavailable → library halts */
size_t copy = (len - i < 4) ? (len - i) : 4;
memcpy(buf + i, &rnd, copy);
}
return 0;
}
int main(void) {
/* Must be called before ratify_challenge_generate() or ratify_delegation_issue() */
ratify_set_entropy_source(my_entropy);
/* … rest of your RTOS application */
}

The library halts if ratify_set_entropy_source() was never called when custom-entropy is enabled — generating certs with weak randomness is worse than not running at all.

The C SDK is compatible with RTOS environments that provide a heap (alloc) but not the full Rust std. Bare-metal Cortex-M targets with no heap at all should use the Rust SDK directly with #[no_std] + alloc.

ArchitectureTarget tripleExample hardware
x86-64x86_64-unknown-linux-gnuIntel/AMD server, Linux PC
ARM64aarch64-unknown-linux-gnuRaspberry Pi 4, embedded Linux, Apple Silicon
ARM32armv7-unknown-linux-gnueabihfRaspberry Pi 2/3, older embedded Linux
ARM Cortex-M4/M7thumbv7em-none-eabihfSTM32, NXP — FreeRTOS, Zephyr
x86-32i686-unknown-linux-gnuLegacy industrial, 32-bit Linux
RISC-V 64riscv64gc-unknown-linux-gnuSiFive, emerging IoT
macOS ARM64aarch64-apple-darwinApple Silicon Mac
macOS x86-64x86_64-apple-darwinIntel Mac
Windows x86-64x86_64-pc-windows-msvcNative Windows

A Raspberry Pi test script is available at sdks/c/scripts/test-raspberry-pi.sh.

The C SDK passes all 63 canonical fixtures — the same set used by Go, TypeScript, Python, and Rust. This includes all verify fixtures (proof-bundle verification, constraints, session/stream binding, revocation) and all non-verify fixture kinds (scope expansion, revocation lists, revocation push, key rotation, session tokens, transaction receipts, witness entries).

Terminal window
# All 63 conformance fixtures
cargo test --test conformance
# Core ABI unit tests (null pointers, malformed JSON, round-trips, bad-argument detection)
cargo test --test api
# Advanced ABI tests (providers, receipt helpers, entropy hooks)
cargo test --test advanced

Total: 123 tests (63 conformance + 60 ABI unit tests across api and advanced).

Every function that returns a heap-allocated value documents which _free function to call. NULL is always safe to pass to _free functions.

ratify_human_root_free(root);
ratify_agent_free(agent);
ratify_delegation_cert_free(cert);
ratify_proof_bundle_free(bundle);
ratify_verify_result_free(result);
ratify_string_free(any_string); /* for *_to_json, *_id, *_status, *_reason */
ratify_error_free(err); /* for err_out parameters */