Talk to the OpenRiC API from code
Every OpenRiC-conformant server exposes the same /api/ric/v1/* surface. This guide shows how to call it from curl, JavaScript, Python, and PHP — enough to build your own capture tool, viewer, analytics pipeline, or integration.
The base URL
Every example uses the reference deployment:
https://ric.theahg.co.za/api/ric/v1
Substitute your own server’s base for production. All endpoints below hang off that root.
Quick sanity check — /health
curl https://ric.theahg.co.za/api/ric/v1/health
# {"status":"ok","service":"RIC-O Linked Data API","version":"1.0"}
No auth needed. If this works from wherever you’re running, the rest of the guide applies.
Read-side: no key needed
Reads are wide-open. Browser clients can hit these cross-origin without any authentication.
Fetch a single record
curl https://ric.theahg.co.za/api/ric/v1/records/egyptian-boat
List records, paginate
curl "https://ric.theahg.co.za/api/ric/v1/records?page=1&per_page=10"
List endpoints follow the openric:items / openric:total / openric:next pattern — see Viewing API §7.
Subgraph — the central endpoint
curl "https://ric.theahg.co.za/api/ric/v1/graph?uri=/informationobject/egyptian-boat&depth=2"
Returns an openric:Subgraph document — {@type, openric:root, openric:depth, openric:nodes, openric:edges}. Feed this directly to @openric/viewer to render.
Search across all entity types
curl "https://ric.theahg.co.za/api/ric/v1/autocomplete?q=egypt&limit=5"
Vocabulary (type pickers)
curl https://ric.theahg.co.za/api/ric/v1/vocabulary/ric_place_type
Drives “Place type” dropdowns — always pull it live; don’t bundle a stale copy.
Write-side: key + scope
The headers
Content-Type: application/json
Accept: application/json
X-API-Key: <your 64-char hex key>
Alternative header names X-REST-API-Key and Authorization: Bearer <key> SHOULD be accepted by conformant servers.
Create a Place
curl -X POST https://ric.theahg.co.za/api/ric/v1/places \
-H "Content-Type: application/json" \
-H "X-API-Key: $RIC_KEY" \
-d '{
"name": "Cradle of Humankind",
"type_id": "region",
"latitude": -25.9333,
"longitude": 27.7667,
"authority_uri": "https://www.geonames.org/1005330"
}'
# 201 Created
# {"id":912500,"slug":"cradle-of-humankind","type":"place","href":"/api/ric/v1/places/cradle-of-humankind"}
Update (PATCH)
curl -X PATCH https://ric.theahg.co.za/api/ric/v1/places/912500 \
-H "Content-Type: application/json" \
-H "X-API-Key: $RIC_KEY" \
-d '{"description": "UNESCO World Heritage Site."}'
# 200 OK
# {"success":true,"id":912500}
Delete
curl -X DELETE https://ric.theahg.co.za/api/ric/v1/places/912500 \
-H "X-API-Key: $RIC_KEY"
# 200 OK
# {"success":true,"id":912500}
Create a relation
curl -X POST https://ric.theahg.co.za/api/ric/v1/relations \
-H "Content-Type: application/json" \
-H "X-API-Key: $RIC_KEY" \
-d '{
"subject_id": 553,
"object_id": 912500,
"relation_type": "took_place_at",
"certainty": "probable",
"evidence": "Per the 1972 excavation report, p.42."
}'
Error shapes
All errors are JSON with a predictable shape:
{ "error": "forbidden", "message": "API key is missing the 'write' scope" }
Common status codes:
| Status | Meaning |
|---|---|
| 200 | OK — success for GET / PATCH / DELETE |
| 201 | Created — success for POST |
| 400 | Bad request — missing or malformed query param |
| 401 | Unauthorized — no / invalid key |
| 403 | Forbidden — key present but missing scope |
| 404 | Not found — unknown entity id / slug / path |
| 422 | Unprocessable entity — body validation failed |
| 429 | Too many requests — rate limit hit (default 60/min) |
| 500 | Server error — bug on the server side |
Client snippets
JavaScript / browser
const API = 'https://ric.theahg.co.za/api/ric/v1';
const key = 'YOUR_KEY';
async function createPlace(data) {
const r = await fetch(`${API}/places`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': key },
body: JSON.stringify(data),
});
if (!r.ok) throw new Error(`${r.status}: ${await r.text()}`);
return r.json();
}
const place = await createPlace({ name: 'Cradle of Humankind' });
console.log(place.slug);
Python
import requests
API = 'https://ric.theahg.co.za/api/ric/v1'
KEY = 'YOUR_KEY'
HEADERS = {'X-API-Key': KEY}
# Read
r = requests.get(f'{API}/autocomplete', params={'q': 'egypt', 'limit': 3})
r.raise_for_status()
for hit in r.json():
print(hit['type'], hit['id'], hit['label'])
# Write
r = requests.post(f'{API}/places', headers=HEADERS, json={'name': 'Cradle of Humankind'})
r.raise_for_status()
place = r.json()
print(place['slug'])
PHP (Laravel’s Http facade)
use Illuminate\Support\Facades\Http;
$api = 'https://ric.theahg.co.za/api/ric/v1';
$key = env('RIC_API_KEY');
$client = Http::withHeaders(['X-API-Key' => $key])->acceptJson();
// Read
$results = $client->get("$api/autocomplete", ['q' => 'egypt', 'limit' => 3])->json();
// Write
$place = $client->asJson()->post("$api/places", [
'name' => 'Cradle of Humankind',
'type_id' => 'region',
])->json();
(This is essentially what Heratio’s RicApiClient.php does — source at github.com/ArchiveHeritageGroup/heratio.)
Shell — one-liner stats
export RIC_KEY="..."
curl -s "https://ric.theahg.co.za/api/ric/v1/places?per_page=1" | jq '."openric:total"'
Rate limits
The reference server caps at 60 requests / minute per IP. Higher-volume workloads should:
- Batch — there’s no bulk endpoint in v0.1, but a tight loop with sleeps is fine up to the limit.
- Ask the operator for a key with a higher rate limit (the
ahg_api_key.rate_limitcolumn can be bumped per key).
CORS
All GET endpoints send Access-Control-Allow-Origin: *. Write endpoints handle the CORS preflight and accept the X-API-Key header from any origin. Browser-based capture clients (like capture.openric.org) work without proxy.
Monitoring / alerting
/health— cheap; poll from your uptime monitor./openapi.json— the full OpenAPI description if you want to auto-generate client bindings.HEADrequests are accepted on most read endpoints.
Next steps
- Viewing API — full endpoint-by-endpoint reference
- Conformance — if you’re building your own OpenRiC server and want to claim conformance
- Architecture — how the reference API fits into the wider ecosystem