Reference · Publications API

Publications API

Two surfaces: public reads (no auth) that any consumer renders from, and authoring (bearer token, scoped to your account) for managing your own publications and their categories. See the Publications concept for the model.

Base URL

Production: https://api.esy.com. All paths below are under /v1/publications.

Public reads — no auth

The headless contract. {slug} is the publication slug; only publications marked public and documents with status published are returned.

GET/v1/publications/public/{slug}/articlesList published documents in a publication.
GET/v1/publications/public/{slug}/articles/{articleSlug}Fetch one published document by slug.
GET/v1/publications/public/{slug}/categoriesList the publication’s categories.
GET /v1/publications/public/esy-research/articles · 200json
{
  "items": [
    {
      "slug": "the-economics-of-desalination",
      "title": "The economics of desalination",
      "description": "Cost drivers, energy intensity, and where the curve bends.",
      "category": "policy",
      "categoryLabel": "Policy",
      "categories": [{ "slug": "policy", "label": "Policy" }],
      "kind": "article",
      "publishedAt": "2026-06-20T14:02:11.004Z",
      "tags": ["water", "energy"],
      "relatedSlugs": []
    }
  ],
  "total": 1
}

Authoring — bearer token

Authenticated requests are scoped to your account: you only see and manage publications you own. Manage tokens from app.esy.com.

GET/v1/publicationsList your publications.
POST/v1/publicationsCreate a publication (secret revealed once).
GET/v1/publications/{id}Fetch one of your publications.
PATCH/v1/publications/{id}Update destination fields.
DELETE/v1/publications/{id}Delete; its documents are unfiled, not deleted.
POST/v1/publications/{id}/secret/rotateMint a new revalidate secret (revealed once).
POST/v1/publications/{id}/verifySend a no-op test webhook and record delivery health.

Create a publication

POST /v1/publicationsjson
{
  "name": "My Field Notes",
  "slug": "my-field-notes",
  "acceptedKinds": ["article"],
  "siteUrl": "https://example.com",
  "sectionPath": "/notes",
  "revalidateUrl": "https://example.com/api/revalidate"
}
The secret is shown once

revalidateSecret is returned only on create and secret/rotate. Esy stores it encrypted and never returns it again — copy it into your consumer’s environment immediately. Later responses only carry hasRevalidateSecret.

201 createdjson
{
  "id": "b4c5d6e7-f8a9-4b0c-8d1e-2f3a4b5c6d7e",
  "name": "My Field Notes",
  "slug": "my-field-notes",
  "acceptedKinds": ["article"],
  "siteUrl": "https://example.com",
  "sectionPath": "/notes",
  "revalidateUrl": "https://example.com/api/revalidate",
  "isPublic": false,
  "hasRevalidateSecret": true,
  "revalidateSecret": "kP3x…shown-once…9aQ",
  "lastDeliveryStatus": "",
  "createdAt": "2026-06-26T07:40:00.000Z"
}

Categories

A publication’s own taxonomy. Authoring endpoints are owner-scoped.

GET/v1/publications/{id}/categoriesList categories.
POST/v1/publications/{id}/categoriesCreate a category.
POST/v1/publications/{id}/categories/reorderReorder by passing ids in the desired order.
PATCH/v1/publications/{id}/categories/{categoryId}Rename or re-slug a category.
DELETE/v1/publications/{id}/categories/{categoryId}Delete a category; documents survive, links drop.

Delivery health

Every webhook attempt records its outcome on the publication so you can see whether your consumer is receiving pings.

FieldMeaning
lastDeliveryStatus“ok” or “failed”.
lastDeliveryAtTimestamp of the last attempt.
lastDeliveryDetailHTTP status or error string for the last attempt.
Versioning

All endpoints are versioned under /v1. Breaking changes land on a new path and are tracked in the changelog.