Credentials
Space credentials are short-lived JWTs for cross-service access to space data. A member requests a delegation token to prove their membership, exchanges the token for a credential JWT, then passes it to an external service that needs to read the space's records.
How credentials work
Credential issuance is a two-step process. The delegation token is a short-lived proof of membership (60-second TTL), and the credential is the bearer token used for cross-service access (2-hour TTL).
Credentials are ES256 JWTs signed with a P-256 keypair unique to each space. The keypair is generated on first credential request and stored encrypted (AES-256-GCM).
Step 1: Get a delegation token
The caller must be an authenticated member of the space. The delegation token is a short-lived proof of membership (60-second TTL).
Note: this endpoint is a GET request (not POST). The previous getMemberGrant endpoint (POST) is available as a legacy alias via dev.happyview.space.getMemberGrant.
const params = new URLSearchParams({
space: "ats://did:plc:abc123/com.example.forum/main",
});
const response = await fetch(`https://happyview.example.com/xrpc/com.atproto.space.getDelegationToken?${params}`, {
headers: {
"X-Client-Key": CLIENT_KEY,
"Authorization": `DPoP ${ACCESS_TOKEN}`,
"DPoP": DPOP_PROOF,
},
});
interface DelegationTokenResponse {
delegationToken: string;
expiresAt: string;
}
const data: DelegationTokenResponse = await response.json();Response:
{
"delegationToken": "eyJhbGciOiJFUzI1NktFWSJ9...",
"expiresAt": "2026-05-09T12:01:00Z"
}Step 2: Get a space credential
Exchange the delegation token for a space credential JWT. The credential is signed by the space's keypair and has a 2-hour TTL.
const response = await fetch("https://happyview.example.com/xrpc/com.atproto.space.getSpaceCredential", {
method: "POST",
headers: {
"X-Client-Key": CLIENT_KEY,
"Authorization": `DPoP ${ACCESS_TOKEN}`,
"DPoP": DPOP_PROOF,
"Content-Type": "application/json",
},
body: JSON.stringify({
grant: "eyJhbGciOiJFUzI1NktFWSJ9...",
}),
});
interface CredentialResponse {
credential: string;
expiresAt: string;
}
const data: CredentialResponse = await response.json();Response:
{
"credential": "eyJhbGciOiJFUzI1NiJ9...",
"expiresAt": "2026-05-09T14:00:00Z"
}Credential claims
The JWT payload contains:
| Claim | Description |
|---|---|
iss | The space authority's DID (who signed it) |
sub | The full ats:// space URI |
iat | Issued at (Unix timestamp) |
exp | Expiry (Unix timestamp) |
jti | Random nonce for replay protection |
Using a credential
Pass the credential as a standard Bearer token in the Authorization header. HappyView distinguishes space credentials from other tokens by checking the JWT header's typ field (atproto-space-credential+jwt).
const response = await fetch(
"https://happyview.example.com/xrpc/com.atproto.space.getRecord?space=...&collection=...&rkey=...",
{
headers: {
"Authorization": `Bearer ${SPACE_CREDENTIAL}`,
},
},
);
const data = await response.json();No DPoP auth or client key is needed when authenticating via space credential — the credential itself is sufficient. The sub claim identifies the space being accessed.
HappyView verifies the credential by resolving the issuer's DID document, extracting the #atproto_space signing key, and validating the JWT signature and expiry. If valid, the request is granted read access to the space identified by sub.
App access control
Before issuing a credential, HappyView checks whether the calling app (identified by its DPoP client key) is allowed to access the space:
open(default): any app can get credentialsallowList: only apps whose client metadata URL appears in theallowedarray can get credentials
For open spaces, requests without a client key are allowed. For allowList spaces, a client key is required — requests without one are rejected.
External credential verification
HappyView can also verify credentials issued by other HappyView instances or space-aware services. When a Bearer space credential is presented, HappyView:
- Decodes the JWT without verification to extract the
iss(issuer DID) - Resolves the issuer's DID document
- Extracts the
#atproto_spacesigning key from the DID doc - Verifies the JWT signature and expiry
- Checks that the
subclaim matches the requested space
A credential issued by one instance can be used to read from another instance that hosts the same space's data.