Free plan

OAuth apps and oEmbed

Build integrations with EveryPage's OAuth 2.0 provider and universal oEmbed embedding.

EveryPage provides two complementary APIs for integrating with documents: OAuth 2.0 for granting third-party apps controlled access to user accounts, and oEmbed for turning any share link into an embedded viewer.

OAuth 2.0

EveryPage acts as an OAuth 2.0 provider, allowing third-party applications to access a user's EveryPage account without handling their credentials. This is fundamentally different from API keys, which carry full account access and cannot be scoped—an OAuth token's permissions are narrowed to exactly what a user grants.

Endpoints and flow

You implement the authorization-code flow with PKCE (RFC 7636). The three endpoints sit at the site root, not under /api:

  • GET /oauth/authorize — User consent screen. Redirect users here with client_id, redirect_uri, response_type=code, code_challenge, code_challenge_method=S256, scope, and optionally state. response_type=code and code_challenge_method=S256 are the only accepted values; the redirect_uri must exactly match one registered for the client, and an unregistered one is rejected with a rendered error rather than a redirect.
  • POST /oauth/token — Exchange an authorization code for tokens, or refresh an access token. Form-encoded. Submit grant_type (one of authorization_code or refresh_token), then either code + redirect_uri + code_verifier, or refresh_token. Client credentials go in HTTP Basic auth, or as client_id/client_secret in the body; a confidential client must present a matching secret. The code_verifier must be 43–128 characters.
  • POST /oauth/revoke — Revoke an access or refresh token per RFC 7009. Submit the token and client credentials.

/oauth/authorize requires a signed-in EveryPage session. A signed-out user is redirected to the login page and returned to the same authorize request afterwards, so your app does not need to handle that case. Once signed in, the browser lands on the consent screen; approving redirects back to your redirect_uri with code and state, and denying redirects back with error=access_denied.

The authorization code expires in 5 minutes and is single-use — it is consumed before the client and PKCE checks run, so a failed exchange cannot be retried. Access tokens expire in 1 hour; refresh tokens in 60 days.

Refresh tokens rotate: each refresh returns a new refresh token and retires the old one. Presenting an already-used refresh token revokes the whole grant and returns invalid_grant. A refresh may pass scope to narrow the grant, never to widen it.

Scopes

The consent screen lists every scope the app requested; the user approves or denies the request as a whole, not scope by scope. The available scopes are:

  • files:read — Read file list and share links.
  • files:write — Upload files, modify file settings, delete files.
  • readership:read — Read readership analytics and viewer sessions.
  • profile — Read the user's email address and subscription plan.
  • webhooks:manage — List, create, delete, and test webhook endpoints.

A client can only request scopes it was registered for; anything outside that set fails with invalid_scope.

An OAuth token holds only the scopes the user granted. By contrast, an API key holds all scopes—so OAuth is the preferred surface for third-party integrations that need only a subset of capabilities.

Client registration

Client registration is not self-service — it is administered by EveryPage, and there is no public registration endpoint. Contact EveryPage to register an OAuth client. Provide your app's name, one or more redirect URIs, and the scopes your integration needs. EveryPage will issue you a client_id and, if your app is confidential (not a browser SPA), a client_secret.

Redirect URIs are matched by exact string comparison. There is no prefix, wildcard, or port-relaxed matching, so register every URI you intend to use — including any local development callback — verbatim.

User revocation

Users see and revoke active OAuth grants under "Connected apps" on their account settings page. Revoking immediately invalidates all tokens for that app.

oEmbed

The GET /oembed endpoint is a public, unauthenticated, per-IP rate-limited oEmbed provider (type rich). It sits at the site root and also answers on owners' custom domains. When a user pastes an EveryPage share link into a consumer that supports oEmbed—WordPress, Notion, or other tools—the consumer queries this endpoint to turn the link into an embedded viewer.

Endpoint

GET /oembed?url=https://everypage.co/{id}&format=json

The url parameter must be an absolute http or https URL pointing to any of EveryPage's identifier forms: the canonical UUID, a 12-character short ID, a variant short ID, or a vanity slug (Pro, on the owner's custom domain). The identifier is read from the first path segment, and an /embed/{id} link is accepted too, so a copied embed src resolves like a share link. A missing or unparseable url is a 400. The endpoint responds with JSON:

{
  "version": "1.0",
  "type": "rich",
  "provider_name": "EveryPage",
  "provider_url": "https://everypage.co",
  "title": "Document title",
  "html": "<iframe src=\"https://everypage.co/embed/{shortId}\" width=\"800\" height=\"600\" frameborder=\"0\" allowfullscreen title=\"Document title\"></iframe>",
  "width": 800,
  "height": 600
}

The default iframe dimensions are 800×600; consumers may restyle them.

Identifier resolution

The oEmbed endpoint resolves every identifier form:

  • UUID — Resolves on everypage.co and on the owner's own custom domain.
  • Short ID — Same as the UUID.
  • Variant short ID — Resolves to the parent file; the emitted embed points to the parent, not the variant.
  • Vanity slug — Resolves only on the owner's active custom domain (or everypage.co if no domain is set).

Every form is checked against the host in the pasted url, not just slugs. If that host is a custom domain registered to a different account, the lookup fails — a branded domain can never vouch for a file it does not own.

Dead links of every shape return a uniform 404:

  • Expired files
  • Burned (revoked) files
  • Non-existent identifiers
  • Unclaimed anonymous uploads (legacy)
  • Revoked link variants
  • An identifier on a foreign custom domain

Returning an identical 404 for all dead states prevents third-party consumers from probing whether a link exists or has been claimed.

Frozen artifact rule

The html field in the response always embeds via the durable /embed/{shortId} URL, never a vanity slug and never a variant identifier. This ensures that the frozen HTML outlives slug renames, domain changes, and variant revocations. Once pasted into a foreign page, the embed remains valid even if the owner changes the link's settings. See embedding for more detail.

Consumer sandboxing

Some oEmbed consumers (like WordPress) treat EveryPage as an untrusted provider and render the embed in a sandboxed iframe with an opaque origin. This prevents the viewer from booting because requests for /assets and session-based public endpoints fail the same-origin check.

The WordPress plugin resolves this by registering EveryPage as a trusted provider on plugin-installed sites, so embeds render without sandboxing. Without the plugin the iframe is inserted but the viewer does not load — there is no partial rendering, and no configuration on your side changes that. Making plugin-less pastes render is a known outstanding platform change (cross-origin headers on assets and public read endpoints, plus cookieless viewing), not something implemented today. See WordPress integration for setup.

Format parameter

Only format=json is supported (XML is deliberately not implemented). Omitting the parameter is equivalent to format=json. Any other value returns 501 Not Implemented.

See also