Skip to main content

Overview

Credentials are qualifications that gate access to workflow steps. The credential system has three main components:
  1. Credential Types: Global definitions (e.g., “dpw_certified”)
  2. User Credentials: Granted credentials with active/revoked status
  3. 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:
  1. System looks up step’s role_id
  2. Checks role’s required_credentials
  3. Queries improver’s active credentials
  4. 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

Build docs developers (and LLMs) love