Collections API

Create, browse, update, and delete collections. A collection is identified by :owner/:slug.


GET /api/collections

No auth required

Browse public collections with optional search.

Query parameters

qSearch by collection name
limitMax results (default 50, max 100)
offsetPagination offset

Response 200

[
  {
    "id": "uuid",
    "slug": "pubpub-archive",
    "name": "PubPub Archive",
    "description": "Full archive of PubPub publications",
    "ownerSlug": "knowledge-futures",
    "ownerName": "Knowledge Futures",
    "latestVersion": "v3.2.0",
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-04-01T00:00:00.000Z"
  }
]

POST /api/accounts/:owner/collections

Auth: write scope

Create a new collection under an account. You must own the account or be a member of the org.

Request

{
  "slug": "my-dataset",
  "name": "My Dataset",
  "public": true
}

Response 201

{
  "id": "uuid",
  "owner": "yourname",
  "slug": "my-dataset",
  "name": "My Dataset"
}

GET /api/collections/:owner/:slug

No auth for public collections

Get collection metadata and latest version summary.

Response 200

{
  "id": "uuid",
  "slug": "pubpub-archive",
  "name": "PubPub Archive",
  "description": "Full archive of PubPub publications",
  "public": true,
  "ownerSlug": "knowledge-futures",
  "ownerName": "Knowledge Futures",
  "createdAt": "2026-01-15T00:00:00.000Z",
  "updatedAt": "2026-04-01T00:00:00.000Z",
  "latestVersion": {
    "semver": "v3.2.0",
    "recordCount": 4521,
    "fileCount": 892,
    "totalBytes": 1073741824,
    "metadata": { "description": "Full archive...", "readme": "..." },
    "createdAt": "2026-04-01T00:00:00.000Z",
    "message": "April sync"
  }
}

PATCH /api/collections/:owner/:slug

Auth: write scope

Update collection metadata. Pass only the fields to change.

Request

{
  "name": "New Name",
  "public": false
}

Response 200

{"ok": true}

DELETE /api/collections/:owner/:slug

Auth: write scope + owner/admin role in the owning org

Delete a collection and all its versions, records, and file references. Files themselves are not deleted (they may be referenced by other collections).

Response 200

{"ok": true}

GET /api/accounts/:owner/collections

No auth required

List all collections belonging to an account. Non-owners see only public collections.

Response 200

[
  {
    "id": "uuid",
    "slug": "pubpub-archive",
    "name": "PubPub Archive",
    "public": true,
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-04-01T00:00:00.000Z"
  }
]

PATCH /api/collections/:owner/:slug/metadata

Auth: write scope

Update version metadata by creating a new patch version. The request body is a JSON object whose fields are merged with the previous version's metadata. Use this to update description, readme, license, or any other metadata fields without pushing new records.

Request

{
  "description": "Updated description of the archive",
  "readme": "# My Collection\nNew readme content.",
  "license": "CC-BY-4.0"
}

Fields

descriptionShort description of the collection.
readmeMarkdown readme content.
licenseLicense identifier (e.g. "CC-BY-4.0").
...Any other key-value pairs. All fields are merged into the previous version's metadata object.

Response 201

{
  "semver": "v3.2.1",
  "hash": "private:e5f6a7b8...",
  "metadata": {
    "description": "Updated description of the archive",
    "readme": "# My Collection\nNew readme content.",
    "license": "CC-BY-4.0"
  }
}

Large collections

A metadata edit creates a patch version, which means recomputing both version digests over the record set and copying every record-membership row. Past a few million records that takes longer than a proxy will hold the connection open. Add ?async=true to get an immediate 202 with a job_id, then poll for the outcome:

PATCH /api/collections/:owner/:slug/metadata?async=true
→ 202 { "job_id": "3f9c…", "status": "running", "base_semver": "v3.2.0" }

GET /api/collections/:owner/:slug/metadata/jobs/3f9c…
→ 200 { "job_id": "3f9c…", "status": "completed",
        "result": { "semver": "v3.2.1", "hash": "private:e5f6a7b8…", "metadata": { … } } }

The job's status is running, completed or failed. On completed, result holds exactly what the synchronous call would have returned; on failed, error holds the rejection. Only one metadata update runs per collection at a time.

Errors

409A metadata update is already in progress for this collection. The response carries its job_id.
422No versions exist yet. Push a version first before updating metadata.

POST /api/collections/:owner/:slug/fork

Auth: write scope

Fork a public collection into a target organization. Creates a new collection under the target org with the source's latest version. Records, schemas, and files are referenced (not copied); zero additional storage.

Only fully-public collections can be forked by a non-member. A fork copies the full record bodies by reference and gives the forker owner-level access to them, so if you are not a member of the source org and the source holds any private content — private records, private types, or private fields — the request is refused with 403 rather than leaked. Members of the source org can always fork.

Request

{
  "targetOrg": "my-org",
  "slug": "my-fork"
}

Fields

targetOrgRequired. Slug of the organization to fork into. You must be a member of this org.
slugOptional slug for the new collection. Defaults to the source collection's slug.

Response 201

{
  "id": "uuid",
  "owner": "my-org",
  "slug": "my-fork",
  "name": "PubPub Archive",
  "forkedFrom": {
    "owner": "knowledge-futures",
    "slug": "pubpub-archive",
    "version": "v3.2.0"
  },
  "version": {
    "semver": "v1.0.0",
    "recordCount": 4521
  }
}

Errors

403Not a member of the target org; or the source holds private content and you are not a member of the source org; or your API key is scoped to specific collections.
404Source collection not found, not public, or target org not found.
409A collection with the same slug already exists in the target org.
422Source collection has no versions to fork.