Overview
Credentials are qualifications that gate access to workflow steps. The credential system has three main components:
- Credential Types: Global definitions (e.g., “dpw_certified”)
- User Credentials: Granted credentials with active/revoked status
- Credential Requests: Improver requests for credentials
Authentication
Most endpoints require authentication via JWT Bearer token. Role requirements vary by endpoint.
Credential Types
GET /credentials/types
Get all available credential types (public endpoint with auth).
Auth Required: withAuth (any authenticated user)
Response: 200 OK
[
{
"value": "dpw_certified",
"label": "DPW Certified Worker",
"description": "Certified by SF Department of Public Works",
"created_at": "2024-01-15T10:00:00Z"
},
{
"value": "sfluv_verifier",
"label": "SFLUV Verifier",
"description": "Trusted community verifier",
"created_at": "2024-01-15T10:00:00Z"
}
]
Usage:
- Displayed in credential request forms for improvers
- Used by proposers when defining workflow roles
- Referenced in issuer scope configuration
Admin Credential Type Management
These endpoints are restricted to admin users only.
GET /admin/credential-types
Get all credential types (admin view with additional details).
Auth Required: withAdmin
Response: 200 OK
Same as public endpoint, but may include additional admin-only metadata.
POST /admin/credential-types
Create a new credential type.
Auth Required: withAdmin
Request Body
{
"value": "fire_safety_certified", // Required: unique identifier
"label": "Fire Safety Certified" // Required: display name
}
Response: 201 Created
{
"value": "fire_safety_certified",
"label": "Fire Safety Certified",
"created_at": "2024-03-15T10:30:00Z"
}
Validation:
value must be unique, lowercase, alphanumeric with underscores
label must not be empty
Error Codes
400: Invalid value format, missing required fields, or value already exists
DELETE /admin/credential-types/
Delete a credential type.
Auth Required: withAdmin
Response: 204 No Content
Error Codes
404: Credential type not found
400: Credential type is in use (referenced by roles, credentials, or scopes)
Safety Check:
Deletion is blocked if the credential type is:
- Required by any workflow role
- Granted to any user (active or revoked)
- In any issuer’s scope list
Credential Request Flow
For Improvers
GET /improvers/credential-requests
Get improver’s own credential requests.
Auth Required: withImprover
Response: 200 OK
[
{
"id": "uuid",
"user_id": "did:privy:...",
"credential_type": "dpw_certified",
"status": "pending",
"requester_name": "Jane Smith",
"requester_email": "[email protected]",
"requested_at": "2024-03-15T10:30:00Z",
"resolved_at": null,
"resolved_by": null,
"resolution_comment": null
}
]
Statuses:
pending: Awaiting issuer decision
approved: Approved (credential granted)
denied: Denied by issuer
POST /improvers/credential-requests
Request a credential.
Auth Required: withImprover
Request Body
{
"credential_type": "dpw_certified"
}
Response: 201 Created
Returns created credential request.
Side Effects:
- Sends email to all issuers with scopes covering this credential type
- Email includes requester details and request ID
Error Codes
400: Invalid credential type
404: Credential type doesn’t exist
409: User already has active credential or pending request for this type
For Issuers
GET /issuers/credential-requests
Get credential requests issuer can review (filtered by issuer’s scopes).
Auth Required: withIssuer
Query Parameters:
search: Filter by requester name/email
page: Page number (default: 0)
count: Results per page (default: 20)
Response: 200 OK
{
"items": [
{
"id": "uuid",
"user_id": "did:privy:...",
"credential_type": "dpw_certified",
"status": "pending",
"requester_name": "Jane Smith",
"requester_email": "[email protected]",
"requested_at": "2024-03-15T10:30:00Z"
}
],
"total": 15,
"page": 0,
"count": 20
}
POST /issuers/credential-requests//decision
Approve or deny a credential request.
Auth Required: withIssuer
Request Body
{
"status": "approved" | "denied",
"decision": "approved" | "denied" // Alternative field name
}
Response: 200 OK
Returns updated credential request.
Side Effects (if approved):
- Creates active credential for user
- Marks request as
approved
- Sets
resolved_at and resolved_by
Error Codes
400: Invalid decision
403: Issuer doesn’t have scope to grant this credential type
404: Request not found
409: User already has pending request or active credential
Direct Credential Management
POST /issuers/credentials
Grant credential directly (bypass request flow).
Auth Required: withIssuer
Request Body
{
"user_id": "did:privy:...",
"credential_type": "dpw_certified"
}
Response: 200 OK
{
"id": "uuid",
"user_id": "did:privy:...",
"credential_type": "dpw_certified",
"granted_by": "did:privy:...",
"granted_at": "2024-03-15T10:30:00Z",
"revoked_at": null,
"is_active": true
}
Use Case: Admin or issuer grants credential without waiting for request.
Error Codes
400: Invalid user_id or credential_type
403: Issuer lacks scope for this credential type
404: User or credential type not found
DELETE /issuers/credentials
Revoke a credential.
Auth Required: withIssuer
Request Body
{
"user_id": "did:privy:...",
"credential_type": "dpw_certified"
}
Response: 200 OK
Side Effects:
- Sets
revoked_at timestamp
- Sets
revoked_by to issuer’s user_id
- Sets
is_active = false
- User can no longer claim steps requiring this credential
- Existing claims/assignments are NOT affected
Error Codes
400: Invalid user_id or credential_type
403: Issuer lacks scope for this credential type
404: Active credential not found
GET /issuers/credentials/
Get all credentials (active and revoked) for a user.
Auth Required: withIssuer
Response: 200 OK
[
{
"id": "uuid",
"user_id": "did:privy:...",
"credential_type": "dpw_certified",
"granted_by": "did:privy:...",
"granted_at": "2024-03-15T10:30:00Z",
"revoked_at": null,
"revoked_by": null,
"is_active": true
}
]
Data Model
Credential Type
{
value: string // Unique identifier (e.g., "dpw_certified")
label: string // Display name
description?: string // Optional description
created_at: string // ISO 8601 timestamp
}
User Credential
{
id: string // UUID
user_id: string // DID of credential holder
credential_type: string // References credential type value
granted_by: string // DID of issuer who granted
granted_at: string // ISO 8601 timestamp
revoked_at?: string // ISO 8601 timestamp (null if active)
revoked_by?: string // DID of issuer who revoked
is_active: boolean // true if not revoked
}
Credential Request
{
id: string // UUID
user_id: string // DID of requester
credential_type: string // Requested credential
status: 'pending' | 'approved' | 'denied'
requester_name: string // From improver profile
requester_email: string // From improver profile
requested_at: string // ISO 8601 timestamp
resolved_at?: string // ISO 8601 timestamp
resolved_by?: string // DID of issuer who resolved
resolution_comment?: string
}
Credential Lifecycle
[Improver] → POST /improvers/credential-requests
↓
[Pending Request]
↓
Email → [Issuers with Scope]
↓
POST /issuers/credential-requests/{id}/decision
↓
Approved/Denied
↓
(if approved) → [Active Credential]
↓
DELETE /issuers/credentials
↓
[Revoked Credential]
Usage in Workflows
Role Configuration
When creating workflows, proposers define roles with required credentials:
{
"roles": [
{
"id": "uuid",
"title": "Certified Field Worker",
"required_credentials": ["dpw_certified"]
}
],
"steps": [
{
"title": "Site Inspection",
"role_id": "uuid" // References role above
}
]
}
Step Claiming
When an improver attempts to claim a step:
- System looks up step’s
role_id
- Checks role’s
required_credentials
- Queries improver’s active credentials
- Allows claim only if improver has ALL required credentials
Improver Workflow Feed
The GET /improvers/workflows endpoint filters workflows:
- Returns
active_credentials array for authenticated improver
- Only includes workflows where improver qualifies for at least one unclaimed step
- Credential check runs before displaying workflows to improvers
Notes
- Credential types are global and shared across all workflows
- Credentials are user-scoped, not workflow-scoped
- Revoking a credential doesn’t unclaim existing assignments
- Admins can grant/revoke any credential type (bypass scopes)
- Issuer scopes control which credential types they can manage
- Credential history (grants and revocations) is preserved