Developers

zvena Docs API

Read and change pages, databases and comments from your own code. Requests and responses follow Notion's API, so many existing clients work after changing the base URL.

Getting started

  1. Docs → Settings → Connections → “New integration”. Pick its capabilities and copy the secret: it is shown once.
  2. Open a page → “•••” → “Connections” and connect the integration. It sees that page and everything under it, nothing else.
  3. Send the secret in the Authorization header.
curl https://zvena.app/api/v1/users/me \
  -H "Authorization: Bearer zv_secret_…" \
  -H "Zvena-Version: 2022-06-28"

Rules

Versions and data sources

A database can hold several data sources, each with its own properties, rows and templates. With a Notion-Version (or Zvena-Version) header of 2025-09-03 or later the API speaks as Notion does: a database lists its data_sources, schemas and rows are read through /v1/data_sources, a row's parent is { type: "data_source_id", data_source_id, database_id }, and search returns data sources (filter value "data_source"). With an older version (2022-06-28 by default) nothing changes: each data source reads as a database with its own id, and the first one is the database itself.

Endpoints

GET/v1/usersPeople and bots of the workspace
GET/v1/users/meThis integration's bot user
GET/v1/users/{id}One user
POST/v1/searchPages and databases by title
POST/v1/pagesA sub-page or a database row
GET/v1/pages/{id}A page and its properties
PATCH/v1/pages/{id}Properties, icon, cover, trash
GET/v1/pages/{id}/properties/{property_id}One property (lists paginated)
POST/v1/databasesA new database inside a page
GET/v1/databases/{id}A database and its schema
PATCH/v1/databases/{id}Title and schema properties
POST/v1/databases/{id}/queryRows with filters and sorts (before 2025-09-03)
POST/v1/data_sourcesA new data source in a database (2025-09-03)
GET/v1/data_sources/{id}A data source and its schema
PATCH/v1/data_sources/{id}Title, properties, trash, move to another database
POST/v1/data_sources/{id}/queryA data source's rows with filters and sorts
GET/v1/data_sources/{id}/templatesA data source's page templates
GET/v1/blocks/{id}A block
PATCH/v1/blocks/{id}Update a block
DELETE/v1/blocks/{id}Delete a block (a page goes to the trash)
GET/v1/blocks/{id}/childrenA page's blocks or a block's children
PATCH/v1/blocks/{id}/childrenAppend blocks (at the end or after `after`)
GET/v1/comments?block_id=Discussions of a page or a block
POST/v1/commentsA comment or a reply

Objects

POST https://zvena.app/api/v1/databases/{id}/query
{
  "filter": { "and": [
    { "property": "Status", "status": { "equals": "Done" } },
    { "property": "Due", "date": { "past_week": {} } }
  ] },
  "sorts": [{ "property": "Points", "direction": "descending" }]
}

Webhooks

Set an https endpoint and events in the integration's settings: page.created, page.content_updated, page.properties_updated, page.deleted, database.created, database.schema_updated, database.deleted, data_source.created, data_source.schema_updated, data_source.deleted, comment.created. Only events of connected pages arrive, never the integration's own changes.

Every request is signed: X-Zvena-Signature: sha256=<HMAC-SHA256 of the body with the signing secret>. The signing secret is shown once when you save the URL. Failed deliveries are retried after 1 s, 10 s and 1 min; private addresses are never called.

{
  "id": "…", "timestamp": "2026-10-14T12:00:00.000Z",
  "type": "page.properties_updated",
  "workspace_id": "…", "integration_id": "…",
  "entity": { "id": "…", "type": "page" },
  "authors": [{ "id": "…", "type": "person" }],
  "data": { "parent": { "id": "…" } },
  "attempt_number": 1
}

Free for everyone. No credit card, no trial, no ads.