Skip to content

API reference

Reference for Prosopia’s HTTP API. Prosopia runs on port 8200 at prosopia.databio.org (profiles.databio.org points to the same app).

Prosopia accepts three kinds of credentials.

A signed, HTTP-only cookie named rp_session, set after ORCID or Google login. The React app uses this automatically. Most /api/manage/* routes require it.

Authorization: Bearer <key>

Two key types, distinguished by prefix:

  • rpk_ (app key): operator-minted, carries app scopes (read, match, persona, push).
  • rpa_ (agent key): owner-minted for one profile, carries agent scopes (profile:metadata, profile:history, profile:identity, profile:narrative, profile:visibility). Cannot use app scopes (403).

The plaintext is returned only once at mint time; the server stores its hash. Each key also carries a viewer tier.

Errors: 401 missing or invalid key; 403 insufficient scope.

Authorization: Bearer <token>

Set by RESEARCHER_PROFILES_TOKEN. Superuser — holds every scope and reads at restricted tier. Administrative, not a shared app credential.

Every read is projected through the caller’s viewer tier: public, internal, or restricted (least to most permissive).

CallerTier
No credentialpublic
Person with no permission from the profile’s ownerpublic
API keythe tier minted on the key (default public)
The owner, a co-owner, or an editor of the profilerestricted
Anyone holding a read or write permission on the ownerrestricted
Operator tokenrestricted

Reading is all-or-nothing: a permission is a whole read.

Response headers: X-RP-Viewer-Tier on every read; X-RP-Effective-Tier on single-artifact reads.

An artifact above the caller’s tier returns 404. An unpublished profile is floored at internal, so anonymous callers see only published profiles.

Session probe. Always 200, never 401. Returns authenticated, login_configured, login_url, user ({orcid, name} or null), owned (profiles you own), claimable, creatable, build_available, is_operator.

Redirects to the OAuth provider. 503 if no login provider is configured.

OAuth callback. Verifies state, exchanges code, sets the session cookie, redirects to /. Errors: 400 invalid state; 502 token exchange failure.

Deletes the session cookie. Returns {"authenticated": false}.

These routes have no credential gate. What the caller sees depends on their viewer tier.

  • GET /api/v1/profiles: profile listing.
  • GET /api/v1/profiles/{slug}: profile detail.
  • GET /api/v1/profiles/{slug}/content/{artifact}: one artifact, projected through the caller’s tier.
  • GET /api/v1/profiles/{slug}/papers: paper inventory.
  • GET /api/v1/profiles/{slug}/summary/{paper_id}: one paper summary.
  • GET /api/v1/registry.json: registry bundle. Cached for anonymous callers; private, no-store for credentialed callers.

{slug} also accepts a rid.

POST /api/manage/profiles — session required.

Request: {"name"?: str, "slug"?: str}. Both optional; name falls back to login name. Creates a minimal private profile (lite, internal) with the ownership row in one transaction.

Response 201: {slug, rid, level, visibility, edit_urls}. Errors: 400 bad name or slug; 401 not logged in; 409 profile already exists or slug taken.

DELETE /api/manage/profiles/{slug} — owner only. Response 204.

Owner or editor (session or agent key with the right scope):

  • PATCH /api/v1/profiles/{slug}/metadata: edit metadata fields.
  • PUT /api/v1/profiles/{slug}/soul: set the narrative.
  • PATCH /api/v1/profiles/{slug}/visibility: set visibility tiers.

An agent key can only narrow visibility, never widen it. Insufficient scope returns 403 with {error, required, granted, missing, hint}.

POST /api/manage/claims — session required.

Claim a profile whose ORCID matches yours. Request: {"slug": "<slug>"}. Response 201: {slug, rid, owned}. Errors: 403 ORCID mismatch; 404 no such profile; 409 already claimed.

GET /api/v1/identity/resolve — gated. Query: rid= or orcid=. Returns {slug, rid, claimed, owner_orcid}. Anonymous callers see only profiles that are published AND publicly visible; a signed-in session, the operator token, or any live rpk_ consumer key sees the rest (restricted profiles answer only to a signed-in session). 404 means “not visible to you” — it does NOT prove the identity is absent, so never treat a 404 here as a green light to create; use POST /api/v1/identity/resolve (the resolver) for that decision.

POST /api/v1/identity/resolve — requires the resolve scope (a write scope: a true miss mints a stub profile). Body: {rid?, name?, affiliation?, create_new?}. Returns {rid, created, confidence}; an undecidable name returns rid: null with candidates — re-resolve with the chosen candidate’s rid, or with create_new: true to mint a fresh identity when no candidate is the caller’s person.

Publication is the owner’s decision to put a profile on the open web. Until published, a profile is floored at internal — anonymous callers get 404.

GET /api/manage/profiles/{ref}/publication

Section titled “GET /api/manage/profiles/{ref}/publication”

Publication state. Owner or editor may read; only the owner sees actor identities and history.

Returns: {slug, rid, published, published_at, revoked_at, profile_visibility, floor, visible_to_anonymous, still_visible_to}.

POST /api/manage/profiles/{ref}/publication

Section titled “POST /api/manage/profiles/{ref}/publication”

Publish or unpublish. Owner only — editors, apps, and the operator token are all refused.

Request: {"published": bool, "confirm"?: str, "profile_visibility"?: str}. Publishing requires confirm equal to the slug. Unpublishing does not. profile_visibility optionally sets the document tier in the same request.

Unpublishing sets an internal floor but is not a blackout: permission holders, managers, internal/restricted keys, and operators keep reading.

A permission is (owner_id, principal, grant_type): may principal read or write everything owner holds — every profile they own and every lens they authored. Keyed on the person whose data it is, never on one profile. write implies read.

  • GET /api/manage/permissions — the signed-in person’s list: every permission others hold on their data.
  • POST /api/manage/permissions — signed-in person. Request: {principal_kind, principal_ref, grant_type}. principal_kind is user (ORCID) or consumer (id/name); grant_type is read or write. Response 201.
  • DELETE /api/manage/permissions/{permission_id} — signed-in person; 404 unless the permission is on their data.

Co-management is per profile and people-only: (profile_rid, user_id, role) with roles co-owner (everything the owner may do except transfer) and editor (rewrite the document; may not publish, delete, or manage access).

  • GET /api/manage/profiles/{slug}/managers — owner or any manager.
  • POST /api/manage/profiles/{slug}/managers — owner only. Request: {orcid, role}. Response 201.
  • DELETE /api/manage/profiles/{slug}/managers/{manager_id} — owner only. Ownership itself is not revocable here. 404 no such manager.

Every consumer overlay on a profile appears to the owner as a suggestion.

  • GET /api/manage/profiles/{slug}/suggestions — owner only.
  • POST …/suggestions/{id}/accept — apply the overlay to the canonical profile. Owner only.
  • POST …/suggestions/{id}/dismiss — mark dismissed; the consumer keeps its private overlay. Owner only.

Prosopia records a build request and hands it to a remote runner. All build endpoints return 503 when no runner is configured.

Two build paths:

  • Self-service: the owner runs the researcher-profile Claude Code skill on their own AI. This does not use any endpoint below. See How to build a profile.
  • Admin-triggered: an operator builds any profile from the admin panel.

POST /api/manage/profiles/admin/build/{ref}

Section titled “POST /api/manage/profiles/admin/build/{ref}”

Enqueue a build. Operator only. Request: {"level": "full"} (or "lite"; "deep" is refused). One build at a time per profile.

Response 202: {job_id, status, level, requested_at, started_at, completed_at, error, phases_completed, runner}. Errors: 409 build already active; 503 no runner.

GET /api/manage/profiles/admin/build/{ref}

Section titled “GET /api/manage/profiles/admin/build/{ref}”

Latest build status. Operator only.

POST /api/manage/profiles/{ref}/build (deprecated)

Section titled “POST /api/manage/profiles/{ref}/build (deprecated)”

Owner-facing enqueue. Deprecated in favor of the self-service skill.

GET /api/manage/profiles/{ref}/build (deprecated)

Section titled “GET /api/manage/profiles/{ref}/build (deprecated)”

Owner-facing status. Deprecated.

POST /api/manage/profiles/{ref}/build/callback

Section titled “POST /api/manage/profiles/{ref}/build/callback”

Runner progress callback. Authenticated by PROSOPIA_BUILD_CALLBACK_TOKEN, not a session. Request: {job_id, status, phases_completed?, error?}.

These require an API key with the appropriate scope.

  • POST /api/v1/match: cross-profile ranking. Scope: match.
  • POST /api/v1/profiles/{slug}/search: semantic search. Scope: match.
  • POST /api/v1/profiles/{slug}/{ask,review,innovate,riff}: persona endpoints. Scope: persona.

An overlay is a consumer-private patch over a canonical profile, keyed by (profile_rid, consumer_id). Only the authoring consumer sees the merged view; everyone else sees canonical alone. Requires an API key (person sessions get 403).

  • GET /api/v1/profiles/{slug}/overlay: the consumer’s overlay and merged view. 404 if none exists, unless ?create=merged.
  • PUT …/overlay/metadata: write the metadata patch. Request: {"patch": {...}}.
  • PUT …/overlay/soul: set or clear the soul override. Request: {"soul": <str|null>}.
  • PATCH …/overlay/visibility: set the visibility patch. Request: {"profile_visibility"?: str, "artifacts"?: [...]}.
  • DELETE …/overlay: discard the overlay.

Write routes return {overlay, merged, stale}. The overlay records the canonical base_hash it was authored against; a later canonical edit marks overlays as stale.

A consumer binds its own external ids (e.g. a faculty member id) to canonical profiles.

  • GET /api/manage/consumers/{consumer}/person-links/{external_id}: resolve the consumer’s id to {consumer_id, external_id, slug, rid}. Must authenticate as the named consumer.
  • POST /api/manage/consumers/{consumer}/person-links: bind one. Request: {external_id, slug?, rid?}. Response 201.
  • GET /api/manage/profiles/{slug}/person-links: owner/editor view of which consumers reference this profile.

An agent is a consumer owned by a person, scoped to write specific fields on that person’s profile via an rpa_ key.

  • GET /api/manage/agents: list your agents.
  • POST /api/manage/agents: mint one. Request: {label, profile, scopes, expires_at?}. Returns the plaintext key once. Response 201.
  • POST /api/manage/agents/{id}/rotate: revoke and re-mint the key.
  • PATCH /api/manage/agents/{id}: update label or scopes.
  • DELETE /api/manage/agents/{id}: revoke the agent and its keys.
  • GET /api/manage/agents/{id}/activity: the agent’s edits and audit.
  • GET /api/manage/agent/whoami: the agent’s authority — principal, owner, profiles, tier, scopes. 400 for app keys.
  • GET /api/manage/agent/scopes: scope catalog. Unauthenticated.

Agent scopes:

ScopeFields
profile:metadatafield, subfields, summary, expertise, interests, not_interests
profile:historytraining, career
profile:identityname, affiliation, job_title, same_as
profile:narrativethe soul narrative
profile:visibilityprofile and artifact tiers (narrow only)

Scope: push (API key or operator token).

  • PUT /api/v1/profiles/{slug}: push a full profile.
  • GET /api/v1/profiles/{slug}/archive: download archive.

Operator-only CRUD for consumer apps and their keys. Requires the operator token or a session on PROSOPIA_ADMIN_ORCIDS.

MethodPathPurpose
GET/api/admin/consumersList all consumers with key counts
POST/api/admin/consumersRegister a consumer (409 duplicate)
GET/api/admin/consumers/{id}Full detail with all keys and audit
PATCH/api/admin/consumers/{id}Update description or active status
DELETE/api/admin/consumers/{id}Deactivate and revoke all keys
POST.../keysMint an rpk_ key
POST.../keys/{key_id}/rotateRevoke and re-mint
POST.../keys/{key_id}/revokeRevoke a key
GET.../auditRecent attributed calls

Valid key scopes: read, match, persona, push. Valid key tiers: public, internal, restricted.

  • GET /health: liveness probe. Unauthenticated.
  • GET /context/v1.jsonld: JSON-LD @context document. Cached, anonymous.

All errors return {"detail": "<string>"} except agent-scope denials, which return a structured detail with error, required, granted, missing, and hint.

Internal developer notes, in the Prosopia source tree rather than on this site: dev-docs/database.md (stored tables), dev-docs/architecture.md (the one-database model), and dev-docs/configuration.md (environment variables).