@@ -23,15 +23,15 @@ There are two auth methods:
2323 POST /api/accounts/logout clears the session.
2424 GET /api/accounts/me returns the current user (works with either auth method).
2525
26- Public endpoints (browsing corpora , reading public versions) require no auth.
26+ Public endpoints (browsing collections , reading public versions) require no auth.
2727Write endpoints require a key with "write" scope or an active session.
2828Delete endpoints require "admin" scope.
2929
3030---
3131
3232## Core Concepts
3333
34- - Corpus : a named, versioned body of data owned by an account. Identified by :owner/:slug.
34+ - Collection : a named, versioned body of data owned by an account. Identified by :owner/:slug.
3535- Version: an immutable snapshot containing a JSON Schema, records, and file references. Sequential integer numbers, auto-derived semver.
3636- Record: a flat JSON object with { id, type, data }. Records reference other records by id and files by hash.
3737- File: a binary blob stored by SHA-256 hash, referenced in record data as {"$file": "sha256:<hex>"}.
@@ -41,27 +41,27 @@ Delete endpoints require "admin" scope.
4141
4242## Reading Data
4343
44- ### Browse corpora
45- GET /api/corpora → list public corpora (?q=search&limit=50&offset=0)
46- GET /api/corpora /:owner/:slug → corpus metadata + latest version summary
44+ ### Browse collections
45+ GET /api/collections → list public collections (?q=search&limit=50&offset=0)
46+ GET /api/collections /:owner/:slug → collection metadata + latest version summary
4747
4848### Read versions
49- GET /api/corpora /:owner/:slug/versions → list versions (newest first, ?limit=50&offset=0)
50- GET /api/corpora /:owner/:slug/versions/latest → latest version with full metadata
51- GET /api/corpora /:owner/:slug/versions/:n → specific version by number
49+ GET /api/collections /:owner/:slug/versions → list versions (newest first, ?limit=50&offset=0)
50+ GET /api/collections /:owner/:slug/versions/latest → latest version with full metadata
51+ GET /api/collections /:owner/:slug/versions/:n → specific version by number
5252
5353### Read records and files
54- GET /api/corpora /:owner/:slug/versions/:n/records → records for a version (?type=TypeName&limit=100&offset=0)
55- GET /api/corpora /:owner/:slug/versions/:n/manifest → lightweight manifest: record ids/types + file hashes
56- GET /api/corpora /:owner/:slug/files/:hash → download a file by hash
57- HEAD /api/corpora /:owner/:slug/files/:hash → check if a file exists (returns Content-Length, Content-Type)
54+ GET /api/collections /:owner/:slug/versions/:n/records → records for a version (?type=TypeName&limit=100&offset=0)
55+ GET /api/collections /:owner/:slug/versions/:n/manifest → lightweight manifest: record ids/types + file hashes
56+ GET /api/collections /:owner/:slug/files/:hash → download a file by hash
57+ HEAD /api/collections /:owner/:slug/files/:hash → check if a file exists (returns Content-Length, Content-Type)
5858
5959### Diff
60- GET /api/corpora /:owner/:slug/versions/:n/diff?from=:m → diff between version m and n (added, updated, removed records)
60+ GET /api/collections /:owner/:slug/versions/:n/diff?from=:m → diff between version m and n (added, updated, removed records)
6161
6262### Export
63- GET /api/corpora /:owner/:slug/export → download .tar.gz archive (manifest.json + records/*.ndjson + files/*)
64- GET /api/corpora /:owner/:slug/export?version=3 → export a specific version
63+ GET /api/collections /:owner/:slug/export → download .tar.gz archive (manifest.json + records/*.ndjson + files/*)
64+ GET /api/collections /:owner/:slug/export?version=3 → export a specific version
6565
6666---
6767
@@ -70,13 +70,13 @@ GET /api/corpora/:owner/:slug/export?version=3 → export a specific versio
7070This is the canonical workflow for syncing an app's data to Underlay:
7171
7272### Step 1: Get current state
73- GET /api/corpora /:owner/:slug/versions/latest
73+ GET /api/collections /:owner/:slug/versions/latest
7474
7575Response includes { number, semver, hash, recordCount, fileCount }.
7676If 404, no versions exist yet — your first push should use base_version: null.
7777
7878### Step 2: Fetch the manifest (optional, for diffing)
79- GET /api/corpora /:owner/:slug/versions/:n/manifest
79+ GET /api/collections /:owner/:slug/versions/:n/manifest
8080
8181Response:
8282{
@@ -92,15 +92,15 @@ Compare this against your local data to determine what changed.
9292### Step 3: Upload new files (if any)
9393For each file your app has that Underlay doesn't:
9494
95- PUT /api/corpora /:owner/:slug/files/sha256:<hex>
95+ PUT /api/collections /:owner/:slug/files/sha256:<hex>
9696Content-Type: application/octet-stream
9797Body: raw file bytes
9898
9999The server verifies the SHA-256 hash matches the body. Existing hashes are idempotent (200 OK).
100100Check existence first with HEAD if you want to skip uploads.
101101
102102### Step 4: Push the version
103- POST /api/corpora /:owner/:slug/versions
103+ POST /api/collections /:owner/:slug/versions
104104Content-Type: application/json
105105Authorization: Bearer ul_<key>
106106
@@ -173,7 +173,7 @@ Set base_version to null. Put all records in changes.added. Include the schema.
173173 }
174174}
175175
176- - id: stable, unique within the corpus . Use your app's primary key or generate a deterministic one.
176+ - id: stable, unique within the collection . Use your app's primary key or generate a deterministic one.
177177- type: groups records by kind (e.g. "Article", "Author", "Grant").
178178- data: flat JSON object. Reference other records by id. Reference files with {"$file": "sha256:<hex>"}.
179179
@@ -191,12 +191,12 @@ The schema declares which fields are references, so tools can resolve them at re
191191
192192---
193193
194- ## Corpus Management
194+ ## Collection Management
195195
196- POST /api/accounts/:owner/corpora → create corpus {"slug", "name", "description", "public"}
197- PATCH /api/corpora /:owner/:slug → update {"name", "description", "public"}
198- DELETE /api/corpora /:owner/:slug → delete corpus (requires admin scope)
199- GET /api/accounts/:owner/corpora → list corpora for an account
196+ POST /api/accounts/:owner/collections → create collection {"slug", "name", "description", "public"}
197+ PATCH /api/collections /:owner/:slug → update {"name", "description", "public"}
198+ DELETE /api/collections /:owner/:slug → delete collection (requires admin scope)
199+ GET /api/accounts/:owner/collections → list collections for an account
200200
201201---
202202
@@ -214,7 +214,7 @@ DELETE /api/accounts/keys/:id → revoke a key
214214400 — Validation error (bad request body)
215215401 — Authentication required
216216403 — Insufficient scope (e.g. read key used for write)
217- 404 — Not found (or private corpus you can't access)
217+ 404 — Not found (or private collection you can't access)
218218409 — Version conflict (re-fetch and retry with new base_version)
219219422 — Missing files (upload them first, then retry push)
220220
0 commit comments