Back to Support Center
Developer API

Public API Documentation

Authentication, endpoints, rate limits, and integration examples.

PressDown Public API

This API is designed for end users who create API keys in Settings -> API Keys.

Authentication

Use your API key as a Bearer token:

Authorization: Bearer pd_live_xxxxxxxxxxxxxxxxxxxxx
  • Base URL (local): http://localhost:3000/api/public/v1
  • Base URL (recommended production via nginx): https://api.pressdown.app
  • Content type: application/json
  • API keys can be revoked at any time from the settings panel.
  • API has dynamic per-token rate limits configured in DB:
    • requests_per_minute (default: 60)
    • stamps_per_minute (default: 6)

You can update limits at runtime (no rebuild) by editing:

update public.api_rate_limits set value_int = 120 where key = 'requests_per_minute';
update public.api_rate_limits set value_int = 12 where key = 'stamps_per_minute';

Endpoints

1) Get credits balance

GET /credits (nginx public host)
or GET /api/public/v1/credits (direct app route)

Returns remaining and total credits for the authenticated user.

cURL

curl -X GET "https://api.pressdown.app/credits" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "credits": {
    "remaining": 842,
    "total": 1200
  }
}

2) List books with published editions

GET /books (nginx public host)
or GET /api/public/v1/books (direct app route)

Returns all user books with nested published_editions.

cURL

curl -X GET "https://api.pressdown.app/books" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "books": [
    {
      "id": "book-uuid",
      "title": "My Book",
      "subtitle": "Optional subtitle",
      "author": "Jane Doe",
      "status": "Published",
      "created_at": "2026-03-15T10:00:00.000Z",
      "updated_at": "2026-03-15T12:00:00.000Z",
      "published_editions": [
        {
          "id": "edition-uuid",
          "version_number": 2,
          "created_at": "2026-03-15T12:00:00.000Z",
          "metadata": {}
        }
      ]
    }
  ]
}

3) Create stamping job

POST /stamps (nginx public host)
or POST /api/public/v1/stamps (direct app route)

Queues a stamping job from a published edition.

Request body

{
  "edition_id": "edition-uuid",
  "stamp_text": "CONFIDENTIAL",
  "format": "pdf",
  "webhook_url": "https://your-app.com/webhooks/pressdown"
}
  • webhook_url is optional but recommended.
  • When provided, PressDown sends a POST webhook after stamping completes.

cURL

curl -X POST "https://api.pressdown.app/stamps" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edition_id": "edition-uuid",
    "stamp_text": "CONFIDENTIAL",
    "format": "pdf",
    "webhook_url": "https://your-app.com/webhooks/pressdown"
  }'

Response

{
  "stamp_id": "stamp-uuid",
  "job_id": "job-uuid",
  "status": "pending",
  "message": "Stamping job queued successfully."
}

Webhook payload (success)

{
  "event": "stamp.completed",
  "stamp_id": "stamp-uuid",
  "job_id": "job-uuid",
  "book_id": "book-uuid",
  "edition_id": "edition-uuid",
  "download_url": "https://...signed-url...",
  "expires_at": "2026-03-16T12:00:00.000Z",
  "generated_at": "2026-03-15T12:00:00.000Z"
}

download_url is a time-limited signed URL (24h) and does not require user session cookies.

Webhook payload (failure)

{
  "event": "stamp.failed",
  "stamp_id": "stamp-uuid",
  "job_id": "job-uuid",
  "book_id": "book-uuid",
  "edition_id": "edition-uuid",
  "error": "Error details"
}

JavaScript Example (Node.js)

const API_KEY = process.env.PRESSDOWN_API_KEY;
const BASE = process.env.PRESSDOWN_API_BASE_URL || "https://api.pressdown.app";

async function api(path, options = {}) {
  const res = await fetch(`${BASE}${path}`, {
    ...options,
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
      ...(options.headers || {})
    }
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `HTTP ${res.status}`);
  }
  return res.json();
}

async function run() {
  const credits = await api("/credits");
  console.log("Credits:", credits);

  const books = await api("/books");
  console.log("Books:", books.books.length);

  const edition = books.books[0]?.published_editions?.[0];
  if (!edition) return;

  const stamp = await api("/stamps", {
    method: "POST",
    body: JSON.stringify({
      edition_id: edition.id,
      stamp_text: "CONFIDENTIAL",
      webhook_url: "https://your-app.com/webhooks/pressdown"
    })
  });
  console.log("Stamp job:", stamp);
}

run().catch(console.error);

Python Example

import os
import requests

BASE = os.environ.get("PRESSDOWN_API_BASE_URL", "https://api.pressdown.app")
API_KEY = os.environ["PRESSDOWN_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

credits = requests.get(f"{BASE}/credits", headers=HEADERS).json()
print("Credits:", credits)

books = requests.get(f"{BASE}/books", headers=HEADERS).json()
print("Books:", len(books.get("books", [])))

books_list = books.get("books", [])
if books_list and books_list[0].get("published_editions"):
    edition_id = books_list[0]["published_editions"][0]["id"]
    stamp = requests.post(
        f"{BASE}/stamps",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={
            "edition_id": edition_id,
            "stamp_text": "CONFIDENTIAL",
            "webhook_url": "https://your-app.com/webhooks/pressdown",
        },
    ).json()
    print("Stamp job:", stamp)

Error format

All errors return:

{
  "error": "Error message"
}

Common status codes:

  • 400 invalid payload
  • 401 missing/invalid API key
  • 402 insufficient credits
  • 404 resource not found
  • 429 rate limit exceeded
  • 500 server error