Documentation Index Fetch the complete documentation index at: https://docs.sphyre.tech/llms.txt
Use this file to discover all available pages before exploring further.
Sphyre Issuers
Sphyre Issuers is the web-based dashboard designed for organizations to issue, manage, and revoke verifiable credentials. It provides a complete workflow for credential lifecycle management.
Overview
Sphyre Issuers enables organizations to become credential issuers in the SSI ecosystem, managing schemas, templates, and the full issuance workflow.
Technology Framework: React 18
Language: TypeScript
URL: https://issuers.sphyre.tech
Key Features Schema management
Template customization
Multi-step issuance
Credential tracking
Key Features
Create custom credential schemas
Define field types and validation rules
Version control for schemas
Import/export schema definitions
Schema library with pre-built templates
Design credential appearance
Custom branding and colors
Logo and image uploads
Preview before publishing
Multiple templates per schema
Manual credential creation
Batch issuance from CSV
QR code generation for offers
Credential request approval workflow
Automated issuance rules
View incoming credential requests
Review applicant information
Approve or reject with reasons
Automated verification checks
Request history and analytics
Revoke credentials instantly
Set expiration dates
Update credential status
Re-issue credentials
Track credential usage
Issuance statistics
Usage metrics
Verification tracking
Export reports
Dashboard insights
Application Structure
sphyre-issuers/
├── src/
│ ├── pages/
│ │ ├── Dashboard.js # Main dashboard
│ │ ├── Schemas.js # Schema management
│ │ ├── Templates.js # Template management
│ │ ├── IssueCredential.js # Issuance workflow
│ │ ├── CredentialRequests.js # Pending requests
│ │ ├── IssuedCredentials.js # Credential tracking
│ │ └── Analytics.js # Statistics
│ ├── components/
│ │ ├── SchemaBuilder.js # Visual schema editor
│ │ ├── TemplateDesigner.js # Template customization
│ │ ├── CredentialForm.js # Dynamic form generator
│ │ ├── RequestCard.js # Request display
│ │ └── QRGenerator.js # QR code creation
│ ├── services/
│ │ ├── apiService.js # API client
│ │ ├── authService.js # Authentication
│ │ └── storageService.js # Local storage
│ ├── contexts/
│ │ ├── AuthContext.js # Auth state
│ │ └── IssuerContext.js # Issuer data
│ └── utils/
│ ├── validation.js # Form validation
│ └── formatting.js # Data formatting
├── public/
│ └── assets/
└── package.json
Core Workflows
1. Schema Creation
Define the structure of credentials you’ll issue.
Schema Builder Interface:
interface CredentialSchema {
id : string ;
name : string ;
version : string ;
description : string ;
fields : FieldDefinition [];
issuerDid : string ;
createdAt : Date ;
}
interface FieldDefinition {
name : string ;
type : 'string' | 'number' | 'date' | 'boolean' | 'email' | 'url' ;
required : boolean ;
description ?: string ;
validation ?: ValidationRule ;
defaultValue ?: any ;
}
Example Schema:
{
"id" : "university-degree-v1" ,
"name" : "University Degree" ,
"version" : "1.0.0" ,
"description" : "Academic degree credential" ,
"fields" : [
{
"name" : "studentName" ,
"type" : "string" ,
"required" : true ,
"description" : "Full legal name of graduate"
},
{
"name" : "degreeType" ,
"type" : "string" ,
"required" : true ,
"validation" : {
"enum" : [ "Bachelor" , "Master" , "Doctorate" ]
}
},
{
"name" : "fieldOfStudy" ,
"type" : "string" ,
"required" : true
},
{
"name" : "graduationDate" ,
"type" : "date" ,
"required" : true
},
{
"name" : "gpa" ,
"type" : "number" ,
"required" : false ,
"validation" : {
"min" : 0.0 ,
"max" : 4.0
}
}
]
}
Schema Builder Component:
const SchemaBuilder = () => {
const [ schema , setSchema ] = useState ({
name: '' ,
version: '1.0.0' ,
fields: []
});
const addField = () => {
setSchema ({
... schema ,
fields: [ ... schema . fields , {
name: '' ,
type: 'string' ,
required: false
}]
});
};
const updateField = ( index , updates ) => {
const newFields = [ ... schema . fields ];
newFields [ index ] = { ... newFields [ index ], ... updates };
setSchema ({ ... schema , fields: newFields });
};
const saveSchema = async () => {
await apiService . createSchema ( schema );
toast . success ( 'Schema created successfully!' );
};
return (
< div className = "schema-builder" >
< Input
label = "Schema Name"
value = { schema . name }
onChange = { ( e ) => setSchema ({ ... schema , name: e . target . value }) }
/>
< div className = "fields-section" >
< h3 > Fields </ h3 >
{ schema . fields . map (( field , index ) => (
< FieldEditor
key = { index }
field = { field }
onChange = { ( updates ) => updateField ( index , updates ) }
/>
)) }
< Button onClick = { addField } > Add Field </ Button >
</ div >
< Button onClick = { saveSchema } variant = "primary" >
Save Schema
</ Button >
</ div >
);
};
2. Template Design
Customize the visual appearance of credentials.
Template Designer:
const TemplateDesigner = ({ schema }) => {
const [ template , setTemplate ] = useState ({
schemaId: schema . id ,
name: '' ,
design: {
backgroundColor: '#1E40AF' ,
textColor: '#FFFFFF' ,
logo: null ,
layout: 'standard'
}
});
const uploadLogo = async ( file ) => {
const formData = new FormData ();
formData . append ( 'logo' , file );
const response = await apiService . uploadImage ( formData );
setTemplate ({
... template ,
design: { ... template . design , logo: response . url }
});
};
return (
< div className = "template-designer" >
< div className = "design-controls" >
< ColorPicker
label = "Background Color"
value = { template . design . backgroundColor }
onChange = { ( color ) => setTemplate ({
... template ,
design: { ... template . design , backgroundColor: color }
}) }
/>
< ImageUpload
label = "Organization Logo"
onUpload = { uploadLogo }
/>
< Select
label = "Layout"
value = { template . design . layout }
options = { [ 'standard' , 'compact' , 'detailed' ] }
onChange = { ( layout ) => setTemplate ({
... template ,
design: { ... template . design , layout }
}) }
/>
</ div >
< div className = "preview" >
< CredentialPreview template = { template } schema = { schema } />
</ div >
</ div >
);
};
3. Credential Issuance Workflow
Multi-step process for issuing credentials.
Issuance Steps:
Select schema and template
Enter credential data
Review information
Issue and sign credential
Generate QR code offer
Implementation:
const IssueCredential = () => {
const [ step , setStep ] = useState ( 1 );
const [ selectedSchema , setSelectedSchema ] = useState ( null );
const [ selectedTemplate , setSelectedTemplate ] = useState ( null );
const [ credentialData , setCredentialData ] = useState ({});
const [ recipient , setRecipient ] = useState ( '' );
const issueCredential = async () => {
try {
// Create credential request
const request = {
schemaId: selectedSchema . id ,
templateId: selectedTemplate . id ,
recipientDid: recipient ,
claims: credentialData ,
issuerDid: getIssuerDid ()
};
// Issue via API
const credential = await apiService . issueCredential ( request );
// Generate QR code offer
const qrCode = await generateQRCode ({
type: 'CredentialOffer' ,
credentialId: credential . id ,
issuer: getIssuerDid ()
});
setQRCode ( qrCode );
toast . success ( 'Credential issued successfully!' );
} catch ( error ) {
toast . error ( 'Failed to issue credential' );
console . error ( error );
}
};
return (
< MultiStepWizard currentStep = { step } >
< Step1_SelectSchema onSelect = { setSelectedSchema } />
< Step2_SelectTemplate
schema = { selectedSchema }
onSelect = { setSelectedTemplate }
/>
< Step3_EnterData
schema = { selectedSchema }
onChange = { setCredentialData }
onRecipientChange = { setRecipient }
/>
< Step4_Review
data = { credentialData }
recipient = { recipient }
onIssue = { issueCredential }
/>
</ MultiStepWizard >
);
};
4. Request Management
Handle incoming credential requests from users.
Request List:
const CredentialRequests = () => {
const [ requests , setRequests ] = useState ([]);
const [ filter , setFilter ] = useState ( 'pending' );
useEffect (() => {
fetchRequests ();
}, [ filter ]);
const fetchRequests = async () => {
const data = await apiService . getCredentialRequests (
getIssuerDid (),
filter
);
setRequests ( data );
};
const approveRequest = async ( requestId ) => {
await apiService . approveCredentialRequest ( requestId );
toast . success ( 'Request approved and credential issued!' );
fetchRequests ();
};
const rejectRequest = async ( requestId , reason ) => {
await apiService . rejectCredentialRequest ( requestId , reason );
toast . info ( 'Request rejected' );
fetchRequests ();
};
return (
< div className = "requests-page" >
< div className = "header" >
< h1 > Credential Requests </ h1 >
< FilterTabs
options = { [ 'pending' , 'approved' , 'rejected' ] }
selected = { filter }
onChange = { setFilter }
/>
</ div >
< div className = "requests-list" >
{ requests . map ( request => (
< RequestCard
key = { request . id }
request = { request }
onApprove = { () => approveRequest ( request . id ) }
onReject = { ( reason ) => rejectRequest ( request . id , reason ) }
/>
)) }
</ div >
</ div >
);
};
Request Card Component:
const RequestCard = ({ request , onApprove , onReject }) => {
const [ showRejectModal , setShowRejectModal ] = useState ( false );
return (
< div className = "request-card" >
< div className = "request-header" >
< h3 > { request . schemaName } </ h3 >
< Badge status = { request . status } />
</ div >
< div className = "request-details" >
< InfoRow label = "Requester DID" value = { truncateDid ( request . holderDid ) } />
< InfoRow label = "Requested" value = { formatDate ( request . createdAt ) } />
< InfoRow label = "Template" value = { request . templateName } />
</ div >
< div className = "request-data" >
< h4 > Submitted Information </ h4 >
{ Object . entries ( request . claims ). map (([ key , value ]) => (
< DataField key = { key } label = { key } value = { value } />
)) }
</ div >
{ request . status === 'pending' && (
< div className = "actions" >
< Button onClick = { onApprove } variant = "success" >
Approve & Issue
</ Button >
< Button onClick = { () => setShowRejectModal ( true ) } variant = "danger" >
Reject
</ Button >
</ div >
) }
{ showRejectModal && (
< RejectModal
onConfirm = { ( reason ) => {
onReject ( reason );
setShowRejectModal ( false );
} }
onCancel = { () => setShowRejectModal ( false ) }
/>
) }
</ div >
);
};
5. Batch Issuance
Issue multiple credentials from CSV file.
const BatchIssuance = () => {
const [ csvData , setCsvData ] = useState ([]);
const [ progress , setProgress ] = useState ( 0 );
const handleFileUpload = ( file ) => {
const reader = new FileReader ();
reader . onload = ( e ) => {
const csv = parseCSV ( e . target . result );
setCsvData ( csv );
};
reader . readAsText ( file );
};
const issueBatch = async () => {
const total = csvData . length ;
for ( let i = 0 ; i < csvData . length ; i ++ ) {
const row = csvData [ i ];
await apiService . issueCredential ({
schemaId: selectedSchema . id ,
templateId: selectedTemplate . id ,
recipientDid: row . did ,
claims: row
});
setProgress ((( i + 1 ) / total ) * 100 );
}
toast . success ( ` ${ total } credentials issued successfully!` );
};
return (
< div className = "batch-issuance" >
< FileUpload
accept = ".csv"
onUpload = { handleFileUpload }
/>
{ csvData . length > 0 && (
<>
< DataPreview data = { csvData . slice ( 0 , 5 ) } />
< p > { csvData . length } credentials ready to issue </ p >
< Button onClick = { issueBatch } > Issue All </ Button >
{ progress > 0 && < ProgressBar value = { progress } /> }
</>
) }
</ div >
);
};
Credential Tracking
View and manage all issued credentials.
const IssuedCredentials = () => {
const [ credentials , setCredentials ] = useState ([]);
const [ searchTerm , setSearchTerm ] = useState ( '' );
const filteredCredentials = credentials . filter ( cred =>
cred . holderName ?. toLowerCase (). includes ( searchTerm . toLowerCase ()) ||
cred . id . includes ( searchTerm )
);
const revokeCredential = async ( credentialId ) => {
if ( confirm ( 'Are you sure you want to revoke this credential?' )) {
await apiService . revokeCredential ( credentialId );
toast . success ( 'Credential revoked' );
fetchCredentials ();
}
};
return (
< div className = "issued-credentials" >
< SearchBar
value = { searchTerm }
onChange = { setSearchTerm }
placeholder = "Search by holder or credential ID"
/>
< Table >
< thead >
< tr >
< th > Credential Type </ th >
< th > Holder </ th >
< th > Issued Date </ th >
< th > Status </ th >
< th > Actions </ th >
</ tr >
</ thead >
< tbody >
{ filteredCredentials . map ( cred => (
< tr key = { cred . id } >
< td > { cred . schemaName } </ td >
< td > { truncateDid ( cred . holderDid ) } </ td >
< td > { formatDate ( cred . issuedAt ) } </ td >
< td >< Badge status = { cred . status } /></ td >
< td >
< Button size = "sm" onClick = { () => viewDetails ( cred ) } >
View
</ Button >
{ cred . status === 'active' && (
< Button
size = "sm"
variant = "danger"
onClick = { () => revokeCredential ( cred . id ) }
>
Revoke
</ Button >
) }
</ td >
</ tr >
)) }
</ tbody >
</ Table >
</ div >
);
};
Analytics Dashboard
Track issuance metrics and statistics.
const Analytics = () => {
const [ stats , setStats ] = useState ( null );
const [ timeRange , setTimeRange ] = useState ( '30d' );
useEffect (() => {
fetchAnalytics ();
}, [ timeRange ]);
const fetchAnalytics = async () => {
const data = await apiService . getAnalytics ( getIssuerDid (), timeRange );
setStats ( data );
};
return (
< div className = "analytics-dashboard" >
< div className = "time-range-selector" >
< Button onClick = { () => setTimeRange ( '7d' ) } > Last 7 Days </ Button >
< Button onClick = { () => setTimeRange ( '30d' ) } > Last 30 Days </ Button >
< Button onClick = { () => setTimeRange ( '1y' ) } > Last Year </ Button >
</ div >
< div className = "stats-grid" >
< StatCard
title = "Total Issued"
value = { stats ?. totalIssued }
icon = "certificate"
/>
< StatCard
title = "Active Credentials"
value = { stats ?. activeCount }
icon = "check-circle"
/>
< StatCard
title = "Revoked"
value = { stats ?. revokedCount }
icon = "ban"
/>
< StatCard
title = "Verifications"
value = { stats ?. verificationCount }
icon = "shield-check"
/>
</ div >
< div className = "charts" >
< IssuanceChart data = { stats ?. issuanceOverTime } />
< CredentialTypesPieChart data = { stats ?. byType } />
</ div >
</ div >
);
};
QR Code Generation
Create QR codes for credential offers.
import QRCode from 'qrcode.react' ;
const QRGenerator = ({ credentialId , issuerDid }) => {
const offerData = {
type: 'CredentialOffer' ,
credentialId ,
issuer: issuerDid ,
url: `https://api.sphyre.tech/api/issuer/offer/ ${ credentialId } `
};
const qrValue = JSON . stringify ( offerData );
const downloadQR = () => {
const canvas = document . getElementById ( 'qr-canvas' );
const url = canvas . toDataURL ( 'image/png' );
const link = document . createElement ( 'a' );
link . download = `credential-offer- ${ credentialId } .png` ;
link . href = url ;
link . click ();
};
return (
< div className = "qr-generator" >
< QRCode
id = "qr-canvas"
value = { qrValue }
size = { 256 }
level = "H"
includeMargin = { true }
/>
< Button onClick = { downloadQR } > Download QR Code </ Button >
</ div >
);
};
Authentication
Issuer organizations authenticate with their DID.
const Login = () => {
const [ did , setDid ] = useState ( '' );
const [ password , setPassword ] = useState ( '' );
const handleLogin = async () => {
try {
const response = await apiService . login ({ did , password });
// Store token
localStorage . setItem ( 'token' , response . token );
localStorage . setItem ( 'issuerDid' , did );
// Redirect to dashboard
navigate ( '/dashboard' );
} catch ( error ) {
toast . error ( 'Login failed. Please check your credentials.' );
}
};
return (
< div className = "login-page" >
< Card >
< h1 > Issuer Login </ h1 >
< Input
label = "Organization DID"
value = { did }
onChange = { ( e ) => setDid ( e . target . value ) }
placeholder = "did:alyra:..."
/>
< Input
label = "Password"
type = "password"
value = { password }
onChange = { ( e ) => setPassword ( e . target . value ) }
/>
< Button onClick = { handleLogin } > Login </ Button >
</ Card >
</ div >
);
};
API Integration
class IssuerApiService {
baseURL = 'https://api.sphyre.tech' ;
async createSchema ( schema ) {
return await this . post ( '/api/issuer/schema' , schema );
}
async issueCredential ( request ) {
return await this . post ( '/api/issuer/issue' , request );
}
async getCredentialRequests ( issuerDid , status = 'pending' ) {
return await this . get ( `/api/issuer/requests?status= ${ status } ` );
}
async approveCredentialRequest ( requestId ) {
return await this . post ( `/api/issuer/request/ ${ requestId } /approve` );
}
async revokeCredential ( credentialId ) {
return await this . post ( `/api/issuer/revoke/ ${ credentialId } ` );
}
async getAnalytics ( issuerDid , timeRange ) {
return await this . get ( `/api/issuer/analytics?range= ${ timeRange } ` );
}
async post ( endpoint , data ) {
const response = await fetch ( ` ${ this . baseURL }${ endpoint } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ this . getToken () } `
},
body: JSON . stringify ( data )
});
return response . json ();
}
async get ( endpoint ) {
const response = await fetch ( ` ${ this . baseURL }${ endpoint } ` , {
headers: {
'Authorization' : `Bearer ${ this . getToken () } `
}
});
return response . json ();
}
getToken () {
return localStorage . getItem ( 'token' );
}
}
export const apiService = new IssuerApiService ();
Best Practices
Verify all data before issuance
Use clear field names and descriptions
Set appropriate expiration dates
Include contact information in templates
Require multi-factor authentication
Audit all issuance actions
Implement role-based access control
Regular security reviews
Provide clear rejection reasons
Fast approval processes
Mobile-responsive design
Helpful error messages
Regular backups
Data retention policies
GDPR compliance
Secure credential storage
Resources
Issuing Guide Complete issuance tutorial
API Reference Issuer API documentation
Live Demo Try Sphyre Issuers