DNAChain REST API
Integrate your LIMS or lab automation platform with DNAChain's tamper-evident chain-of-custody. Register samples, initiate transfers, and pull consent and audit records over HTTPS with scoped API keys.
Base URL
All endpoints are served from a single stable HTTPS origin:
https://api.dnachain.bio/v1All requests must use HTTPS. All responses are JSON.
Authentication
Every request must include an Authorization header with a Bearer API key. Admins can create keys in Settings → API keys.
curl "https://api.dnachain.bio/v1/samples" \
-H "Authorization: Bearer dna_live_YOUR_KEY_HERE"Keys carry one or more scopes. A request missing the required scope returns 403 INSUFFICIENT_SCOPE.
Endpoints
/samplessamples:readList samples for your organization. Supports pagination and filters.
curl "https://api.dnachain.bio/v1/samples?limit=50&status=Anchored" \
-H "Authorization: Bearer dna_live_YOUR_KEY"{
"data": [{
"id": "…",
"sample_code": "SMP-001",
"donor_ref": "D-123",
"sample_type": "Whole Blood",
"collected_at": "2026-06-01",
"custodian": "Dr. Smith",
"location": "Cambridge, MA",
"chain_status": "Anchored",
"consent_status": "Active",
"current_hash": "a3f2…",
"created_at": "2026-06-01T10:22:11Z"
}],
"total": 128, "limit": 50, "offset": 0
}/samplessamples:writeRegister a new sample. Returns the genesis hash and initial chain status.
curl -X POST "https://api.dnachain.bio/v1/samples" \
-H "Authorization: Bearer dna_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"sample_code": "SMP-001",
"donor_ref": "D-123",
"sample_type": "Whole Blood",
"collected_at": "2026-06-01",
"custodian": "Dr. Smith",
"location": "Cambridge, MA",
"irb_protocol": "IRB-2026-01",
"consent_type": "Broad research",
"consented_at": "2026-06-01"
}'{
"id": "…",
"sample_code": "SMP-001",
"hash": "a3f2…",
"chain_status": "Pending",
"created_at": "2026-06-01T10:22:11Z"
}/samples/:idsamples:readFetch a single sample with its full custody event history and consent records.
{
"sample": { "id": "…", "sample_code": "SMP-001", "…": "…" },
"custody_events": [
{ "event_type": "registered", "to_custodian": "Dr. Smith",
"event_hash": "a3f2…", "created_at": "…" }
],
"consent_records": [
{ "consent_type": "Broad research", "status": "Active", "expires_at": null }
]
}/transferstransfers:readList transfers for your organization.
{
"data": [{
"id": "…", "sample_id": "…",
"from_custodian": "Dr. Smith", "to_custodian": "Dr. Chen",
"status": "In Transit", "initiated_at": "…", "current_hash": "…"
}],
"total": 12, "limit": 50, "offset": 0
}/transferstransfers:writeInitiate a transfer. The sample's custody chain is extended atomically.
curl -X POST "https://api.dnachain.bio/v1/transfers" \
-H "Authorization: Bearer dna_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"sample_id": "…",
"to_custodian": "Dr. Chen",
"to_location": "Boston, MA"
}'{
"id": "…",
"sample_id": "…",
"status": "In Transit",
"hash": "b7e8…",
"initiated_at": "…"
}/consentconsent:readList consent records with expiry status. Filter by sample_id or status.
{
"data": [{
"id": "…", "sample_id": "…", "donor_ref": "D-123",
"consent_type": "Broad research", "irb_protocol": "IRB-2026-01",
"status": "Active", "expires_at": "2027-06-01", "days_until_expiry": 322
}],
"total": 128, "limit": 50, "offset": 0
}/auditaudit:readPull audit log entries. Supports action, from_date, and to_date filters. Max limit 500.
{
"data": [{
"id": "…", "action": "sample.registered",
"table_name": "samples", "record_id": "…",
"actor_id": "…", "created_at": "…"
}],
"total": 1284, "limit": 100, "offset": 0
}/integrity/:sample_idsamples:readRecompute and verify the full custody hash chain for a sample.
{
"sample_id": "…",
"integrity_ok": true,
"chain_status": "Anchored",
"checked_at": "2026-07-14T12:00:00Z"
}Rate limits
Rate limits are enforced per API key, per minute:
- Read endpoints: 100 requests / minute
- Write endpoints: 20 requests / minute
Every response includes:
X-Request-ID: <uuid>
X-RateLimit-Remaining: <n>
X-RateLimit-Reset: <unix-epoch-seconds>When exceeded, the API returns 429 RATE_LIMITED with a Retry-After header indicating seconds until reset.
Error format
All errors share a uniform envelope:
{
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid or missing API key"
}
}Code examples
curl
curl "https://api.dnachain.bio/v1/samples?limit=10" \
-H "Authorization: Bearer dna_live_YOUR_KEY"Python
import requests
API_KEY = "dna_live_YOUR_KEY"
BASE = "https://api.dnachain.bio/v1"
r = requests.get(f"{BASE}/samples",
headers={"Authorization": f"Bearer {API_KEY}"})
r.raise_for_status()
print(r.json())
# Register a sample
r = requests.post(f"{BASE}/samples",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"sample_code": "SMP-001",
"donor_ref": "D-123",
"sample_type": "Whole Blood",
"collected_at": "2026-06-01",
"custodian": "Dr. Smith",
"location": "Cambridge, MA",
"irb_protocol": "IRB-2026-01",
"consent_type": "Broad research",
"consented_at": "2026-06-01",
})
print(r.json())JavaScript
const API_KEY = "dna_live_YOUR_KEY";
const BASE = "https://api.dnachain.bio/v1";
// List samples
const res = await fetch(`${BASE}/samples?limit=10`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { data, total } = await res.json();
console.log(`${total} samples`, data);
// Register a sample
const created = await fetch(`${BASE}/samples`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sample_code: "SMP-001",
donor_ref: "D-123",
sample_type: "Whole Blood",
collected_at: "2026-06-01",
custodian: "Dr. Smith",
location: "Cambridge, MA",
irb_protocol: "IRB-2026-01",
consent_type: "Broad research",
consented_at: "2026-06-01",
}),
}).then((r) => r.json());
console.log(created);