Skip to main content
The Integration Guide is for anyone wiring Open Notes into a new platform. Every integration — Discourse today, Mattermost or Ghost tomorrow — speaks the same public API contract:
  • Authenticate with a platform:adapter-scoped API key minted on platform.opennotes.ai.
  • Identify end users with X-Adapter-* headers on every request.
  • Drive the same set of endpoints under /api/public/v1.
  • React to community consensus via webhooks (or polling as a fallback).

How to read this guide

1

Onboarding

Start on platform.opennotes.ai — register your community, add your instance, mint an API key, and pick a moderation tier. This unlocks everything else.
2

Concepts

Learn the three things every request carries: an API key (scope), identity headers (user context), and the endpoints those keys can reach.
3

Integration walkthrough

Walk through the lifecycle of a single piece of content: classification request → community review → webhook-driven action. This is the core loop every integration implements.

The public API contract

An integration is any service that connects a community platform (Discourse, Discord, Reddit, …) to the Open Notes API. The public API contract defines the exact responsibilities on each side. Follow it and your integration gets scoring, community review, and decision delivery automatically.

What an integration does

Submit content

Call POST /api/public/v1/requests with the content and platform context headers whenever a user posts or a moderator flags something.

Carry user identity

Send X-Adapter-* headers so Open Notes knows who submitted the content. The server uses this to weight rater influence and detect patterns.

Apply decisions

Listen for moderation.decision webhooks and apply the action on the source platform (hide, delete, flag, notify).

Record actions

After applying a decision, call POST /api/public/v1/moderation-actions so the audit trail is complete.

What the server guarantees

GuaranteeDetail
Idempotent submissionSubmitting the same request_id twice returns the existing record, not a duplicate.
At-least-once deliveryWebhooks are retried up to 3 times with exponential backoff (10 s, 30 s, 90 s).
Authenticated identityX-Platform-* spoofing from external callers is stripped by middleware. Only valid platform:adapter keys can attach user context via X-Adapter-*.
Stable IDsRequest IDs, note IDs, and rating IDs are UUID v7 and never reused.
Public API isolationThe /api/public/v1 surface is versioned and will not break without a major version bump and a deprecation notice.

Minimum viable integration

A minimal integration needs exactly three things:
  1. A platform:adapter-scoped API key (see Mint API keys).
  2. Logic to call POST /api/public/v1/requests when content should be reviewed.
  3. A webhook endpoint that receives moderation.decision events and applies the action.
The Quickstart shows the minimum call. You can grow from there.

Header shape (summary)

Every request your integration makes must include:
Authorization: Bearer <api_key>
X-Adapter-Platform: discourse        # or: discord, reddit, custom
X-Adapter-User-Id: <platform_user_id>
X-Adapter-Username: <username>
X-Adapter-Trust-Level: <0-4>
X-Adapter-Admin: false
X-Adapter-Moderator: false
X-Adapter-Scope: <community_server_id>
See Headers and auth for the full semantics and failure modes.

Security boundary

The server enforces two layers of security:
  1. API key authentication — the Authorization: Bearer header is checked first. A missing or invalid key returns HTTP 401.
  2. Scope check — the key must include platform:adapter. Any key without this scope returns HTTP 403 on integration endpoints.
X-Platform-* headers (used by internal services) are stripped from all external requests by InternalHeaderValidationMiddleware before they reach any route handler. External integrations cannot spoof platform-level identity — they can only assert user-level identity via X-Adapter-*, which is gated behind the platform:adapter scope check.

The Discourse plugin as reference implementation

The Discourse plugin is the canonical reference. It implements the full public API contract end-to-end in production. If this guide is ambiguous, treat the Discourse plugin as the source of truth — its source code is the tie-breaker.
Contract pieceDiscourse plugin implementation
Submit contentOpenNotes::Client#post("/api/public/v1/requests", ...) called from a PostCustomField job
Carry user identityexecute_request sets all six X-Adapter-* headers from the Discourse User object
Apply decisionsDiscourse Silence / Hide Post / Delete API called from the webhook handler
Record actionsPOST /api/public/v1/moderation-actions called after every platform action
AuthX-API-Key header with the key stored in the opennotes_api_key site setting
The Ruby client lives at plugin/lib/opennotes/client.rb. Reading it is the fastest way to understand the full flow.

Walkthrough

For a step-by-step end-to-end guide with real examples see Integration walkthrough.