Skip to content

Authentication

The Researcher Profiles specification does not require authentication. A server MAY serve profiles publicly with no credentials. This is the norm for static file hosts (S3, R2, GitHub Pages).

A server MAY require authentication on any endpoint. When it does, it MUST use bearer tokens:

Authorization: Bearer <token>

An unauthenticated request to a protected endpoint MUST receive 401 with {"detail": "invalid or missing bearer token"}.

A request for a profile or artifact above the caller’s access level returns 404, indistinguishable from a nonexistent resource.


A server MAY implement viewer tiers to control which artifacts a caller can see. Three tiers are defined:

TierSees
publicPublic artifacts only
internalPublic + internal artifacts
restrictedAll artifacts

An unauthenticated caller defaults to the public tier. A server that does not implement tiers treats all callers as public.

When viewer tiers are active, responses SHOULD include an X-RP-Viewer-Tier header indicating the caller’s effective tier.


A server that mints credentials SHOULD use prefixed, opaque bearer tokens so a key names its own family on sight. Two families are defined.

PrefixFamilyHeld byPurpose
rpk_appAn application, a script, or a person’s own command lineRead, push, and other whole-application powers
rpa_agentAn assistant acting for one person, on one profileNarrow, named write scopes

A single key MUST NOT mix family-exclusive write scopes. An rpa_ key MUST NOT carry app-only scopes (match, persona, push, etc.), and an rpk_ key MUST NOT carry agent write scopes (profile:metadata, profile:narrative, etc.). The read scope is family-neutral and MAY appear on either prefix. A server MUST reject a mint request that mixes exclusive scopes. On data endpoints the prefix is a label, never an authorization input: scopes and grants decide, and a key of either family is judged by what it holds. Only on the management surfaces (such as GET /api/manage/agent/whoami) MUST a server refuse a key of the other family with 400 rather than 401, naming the endpoint that would have worked.

A key’s plaintext MUST be shown exactly once, at mint time. A server MUST store only a hash of it, never the plaintext. A server SHOULD store a non-secret display prefix so a person can recognize a key in a list without revealing it.

A server MUST reject a key that is unknown, revoked, or past its expiry, and MUST make those three cases indistinguishable in the response, so an attacker learns nothing from probing.

These are family-neutral and MAY appear on either rpk_ or rpa_ keys.

ScopeGrants
readRead profiles at the key’s viewer tier

These attach to rpk_ keys. A server MAY define others.

ScopeGrants
matchCross-profile matching
personaThe persona endpoints
pushUpload any profile
push_ownUpload only profiles the key’s owning person holds an owner or editor role on
resolveResolve a person descriptor to a rid, minting an internal stub on a miss

push_own is what the command-line login flow mints, and it is why the flow is safe to expose to any person with an account: a key that escapes can still only touch profiles that person already controls.

These attach to rpa_ keys. They are the vocabulary published by GET /api/manage/agent/scopes. Every one is a write scope; the read capability is the shared read scope above. There is no wildcard, and neither family implies the other.

ScopeCoversFieldsDangerousDefault
profile:metadataPATCH /profiles/{slug}/metadatafield, subfields, summary, expertise, interests, not_interestsnoon
profile:historyPATCH /profiles/{slug}/metadatatraining, careernoon
profile:identityPATCH /profiles/{slug}/metadataname, affiliation, job_title, same_asnooff
profile:narrativePUT /profiles/{slug}/soulnoon
profile:visibilityPATCH /profiles/{slug}/visibilityyesoff

profile:visibility is marked dangerous because an agent holding it on a published profile can put content behind a wall. It MUST allow narrowing only. A request that would raise a tier (restricted to internal, or internal to public) MUST be refused with 403 naming the artifact, its current tier, and the requested tier.

A server MUST NOT define a scope that delegates any of the following. These are decisions belonging to the person the profile describes.

ActWhy
Publish or unpublishPublishing is a decision by the person the profile describes.
Delete the profileOnly the owner may delete a profile.
Grant or revoke accessOnly the owner may grant access, including minting another agent.
Mint or revoke an agent keyA leaked key must not be able to mint a successor.
Widen visibilityMaking something more visible is an exposure decision.

A server SHOULD publish this list at GET /api/manage/agent/whoami as never_delegable, so an agent can tell its user what it cannot be given.

A request whose key is valid but lacks the required scope MUST return 403 with this body, not 401 and not a bare {"detail": ...}:

FieldTypeDescription
errorstringThe literal "insufficient_scope"
requiredlist[string]Scopes this request needs
grantedlist[string]Scopes the key holds
missinglist[string]required minus granted
hintstringOne sentence naming the blocked fields and the fix
{
"error": "insufficient_scope",
"required": ["profile:identity"],
"granted": ["profile:metadata", "profile:history", "profile:narrative"],
"missing": ["profile:identity"],
"hint": "The fields ['name'] require scope(s) ['profile:identity']. Ask your owner to add them."
}

A client MUST NOT retry on this response. It should report the missing scope and stop.