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).
Authentication
Section titled “Authentication”Prosopia accepts three kinds of credentials.
Session cookie
Section titled “Session cookie”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.
API key
Section titled “API key”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.
Operator token
Section titled “Operator token”Authorization: Bearer <token>Set by RESEARCHER_PROFILES_TOKEN. Superuser — holds every scope and reads
at restricted tier. Administrative, not a shared app credential.
Viewer tiers
Section titled “Viewer tiers”Every read is projected through the caller’s viewer tier: public,
internal, or restricted (least to most permissive).
| Caller | Tier |
|---|---|
| No credential | public |
| Person with no permission from the profile’s owner | public |
| API key | the tier minted on the key (default public) |
| The owner, a co-owner, or an editor of the profile | restricted |
Anyone holding a read or write permission on the owner | restricted |
| Operator token | restricted |
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 and login
Section titled “Session and login”GET /api/manage/session
Section titled “GET /api/manage/session”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.
GET /api/manage/login
Section titled “GET /api/manage/login”Redirects to the OAuth provider. 503 if no login provider is configured.
GET /api/manage/callback
Section titled “GET /api/manage/callback”OAuth callback. Verifies state, exchanges code, sets the session cookie,
redirects to /. Errors: 400 invalid state; 502 token exchange failure.
POST /api/manage/logout
Section titled “POST /api/manage/logout”Deletes the session cookie. Returns {"authenticated": false}.
Reading profiles
Section titled “Reading profiles”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-storefor credentialed callers.
{slug} also accepts a rid.
Creating a profile
Section titled “Creating a profile”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.
Deleting a profile
Section titled “Deleting a profile”DELETE /api/manage/profiles/{slug} — owner only. Response 204.
Editing a profile
Section titled “Editing a profile”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}.
Claiming a profile
Section titled “Claiming a profile”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.
Identity resolution
Section titled “Identity resolution”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
Section titled “Publication”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.
Permissions
Section titled “Permissions”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_kindisuser(ORCID) orconsumer(id/name);grant_typeisreadorwrite. Response201. - DELETE /api/manage/permissions/{permission_id} — signed-in person;
404unless the permission is on their data.
Managers
Section titled “Managers”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}. Response201. - DELETE /api/manage/profiles/{slug}/managers/{manager_id} — owner only.
Ownership itself is not revocable here.
404no such manager.
Suggestions
Section titled “Suggestions”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.
On-demand builds
Section titled “On-demand builds”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-profileClaude 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?}.
Search and AI
Section titled “Search and AI”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.
Overlays
Section titled “Overlays”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.
404if 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.
Person links
Section titled “Person links”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?}. Response201. - GET /api/manage/profiles/{slug}/person-links: owner/editor view of which consumers reference this profile.
Agents
Section titled “Agents”An agent is a consumer owned by a person, scoped to write specific fields on
that person’s profile via an rpa_ key.
Managing agents (session required)
Section titled “Managing agents (session required)”- GET /api/manage/agents: list your agents.
- POST /api/manage/agents: mint one. Request:
{label, profile, scopes, expires_at?}. Returns the plaintext key once. Response201. - 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.
Agent self-service (agent key required)
Section titled “Agent self-service (agent key required)”- GET /api/manage/agent/whoami: the agent’s authority — principal, owner,
profiles, tier, scopes.
400for app keys. - GET /api/manage/agent/scopes: scope catalog. Unauthenticated.
Agent scopes:
| Scope | Fields |
|---|---|
profile:metadata | field, subfields, summary, expertise, interests, not_interests |
profile:history | training, career |
profile:identity | name, affiliation, job_title, same_as |
profile:narrative | the soul narrative |
profile:visibility | profile and artifact tiers (narrow only) |
Push and archive
Section titled “Push and archive”Scope: push (API key or operator token).
- PUT /api/v1/profiles/{slug}: push a full profile.
- GET /api/v1/profiles/{slug}/archive: download archive.
Consumer admin
Section titled “Consumer admin”Operator-only CRUD for consumer apps and their keys. Requires the operator
token or a session on PROSOPIA_ADMIN_ORCIDS.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/admin/consumers | List all consumers with key counts |
| POST | /api/admin/consumers | Register 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 | .../keys | Mint an rpk_ key |
| POST | .../keys/{key_id}/rotate | Revoke and re-mint |
| POST | .../keys/{key_id}/revoke | Revoke a key |
| GET | .../audit | Recent attributed calls |
Valid key scopes: read, match, persona, push.
Valid key tiers: public, internal, restricted.
Other endpoints
Section titled “Other endpoints”- GET /health: liveness probe. Unauthenticated.
- GET /context/v1.jsonld: JSON-LD
@contextdocument. Cached, anonymous.
Errors
Section titled “Errors”All errors return {"detail": "<string>"} except agent-scope denials, which
return a structured detail with error, required, granted, missing,
and hint.
Related
Section titled “Related”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).