PathForge API

A read-only JSON API for your character data — build Discord bots, embeds, companion apps, or your own dashboards. Public endpoints respect each character's privacy settings; authenticated endpoints read your own characters with a scoped key.

Base URL
https://www.pfsheet.org/api/v1
Version / discovery
GET /api/v1 · openapi.json

Quick start

Public data needs no auth — just a character's public slug (from its share link):

curl https://www.pfsheet.org/api/v1/public/characters/{publicSlug}/summary

For your own characters, create a key under Settings → API keys and send it as a Bearer token:

curl -H "Authorization: Bearer pf_live_..." \
  https://www.pfsheet.org/api/v1/characters/{characterId}/summary

Authentication & scopes

Keys look like pf_live_…and are shown once at creation — store them securely. A key only ever reads its owner's characters, and you can restrict a key to specific characters. Each scope unlocks a slice of data:

characters:read

Full read of your characters — every section you can see (implies the narrower character scopes).

characters:summary

Computed summary: level, HP, AC, CMB, CMD, saves, initiative, speed.

characters:portrait

Character name and portrait URL only.

discord:embed

Discord character cards for your own characters.

campaigns:read

Read your campaigns. Reserved — no endpoints use it yet.

Response shape

Every response is wrapped in a consistent envelope with a request id and version:

// success
{ "data": { /* ... */ }, "meta": { "requestId": "…", "version": "v1" } }

// error
{ "error": { "code": "not_found", "message": "…" }, "meta": { "requestId": "…", "version": "v1" } }

Endpoints

Public

GET/api/v1/healthpublic

Liveness check — confirms the API is up.

Returns: { status, service, time }.

GET/api/v1/public/characters/{publicSlug}/summarypublic

Public character summary (public-safe values only).

Returns: Summary: name, classLine, level, race, alignment, hp, ac, cmb, cmd, saves, initiative, speed, spellcasting (roll-up: casterCount/highestLevel/slotsRemaining).

GET/api/v1/public/characters/{publicSlug}/statspublic

Public stats — summary plus abilities, and skills/attacks/spellcasting if the share settings allow.

Returns: Summary + abilities[] + skills[]|null + attacks[]|null + spellcasting|null (casters/slots/prepared/known/spellbook/counts).

GET/api/v1/public/characters/{publicSlug}/portraitpublic

Public portrait reference.

Returns: { name, portraitUrl }.

GET/api/v1/public/characters/{publicSlug}/opengraphpublic

OpenGraph metadata for link embeds.

Returns: { title, description, image }.

Authenticated

GET/api/v1/characters/{characterId}/summaryscope: characters:summary

Summary for one of your own characters (full values).

Returns: Summary (same shape as the public summary).

GET/api/v1/characters/{characterId}/statsscope: characters:read

Full stats for your character — abilities, skills, attacks, spellcasting.

Returns: Summary + abilities[] + skills[] + attacks[] + spellcasting (casters/slots/prepared/known/spellbook/counts).

GET/api/v1/characters/{characterId}/portraitscope: characters:portrait

Portrait reference for your character.

Returns: { name, portraitUrl }.

GET/api/v1/characters/{characterId}/sharescope: characters:read

Share metadata for your character.

Returns: { visibility, publicSlug, shareUrl }.

Discord

GET/api/v1/discord/character-cardpublic · or scope: discord:embed

Discord character card. Use ?slug= for a public character (no auth), or ?characterId= with a key that has discord:embed.

  • slug (optional)Public slug — returns the public-safe card, no auth.
  • characterId (optional)Your character's id — requires a key with discord:embed.

Returns: Card: name, subtitle, portraitUrl, level, hp, ac, saves, initiative, speed, topSkills[], activeBuffs[], preparedHighlights[], shareUrl.

Rate limits

Public endpoints: 120 requests / minute (per client IP)
Authenticated endpoints: 240 requests / minute (per API key (or signed-in user))

Over the limit returns 429 rate_limited. Limits are a safety valve and may change.

Errors

StatusCodeWhen
400bad_requestRequired query parameters are missing.
401unauthorizedNo valid API key (or session) was supplied.
403forbiddenThe key lacks the scope, or the character isn't yours.
404not_foundNo public character with that slug, or character not found.
422invalid_characterThe character data failed validation.
429rate_limitedToo many requests in the current window.

Privacy

Public endpoints serve only what a character's share settings expose — the same privacy model as the public sheet. Sections a viewer can't see come back nullor empty, never as raw data. Private and campaign-only characters aren't reachable through the public API at all.

Ready to build? Create an API key →