API Routes Documentation
November 24, 2025 ยท View on GitHub
Version: 2.0.0 (Multi-DAO)
Last Updated: November 24, 2025
๐ Authentication
All routes except /auth/* and /health require authentication via Bearer token.
Token Format: Authorization: Bearer <token>
Multi-DAO Context
Most DAO-related endpoints use the addDaoContext middleware which automatically determines which DAO(s) the authenticated user belongs to. You can specify a DAO in two ways:
- Query Parameter:
?multisig=<multisig-address> - Header:
X-Multisig-Address: <multisig-address>
If not specified, the system uses the first DAO the user's wallet belongs to.
๐ Routes Overview
Auth Routes (/auth)
POST /auth/web3-login- Authenticate with Web3 walletGET /auth/verify- Verify token validityPOST /auth/logout- Logout userGET /auth/profile- Get user profile
DAO Routes (/dao)
GET /dao/:daoId- Get DAO informationGET /dao/:daoId/stats- Get DAO statisticsPOST /dao/register- Register new DAOGET /dao/members- Get DAO membersGET /dao/:daoId/members/:chain- Get members for specific DAOPOST /dao/:daoId/members/:chain/refresh- Refresh member cacheGET /dao/:daoId/members/:chain/:walletAddress- Check membershipGET /dao/parent- Get parent addressGET /dao/config- Get DAO configurationGET /dao/my-assignments- Get user's assignmentsGET /dao/workflow- Get workflow dataPOST /dao/sync- Sync with PolkassemblyPOST /dao/cleanup-duplicate-actions- Cleanup utility
Referendum Routes (/referendums)
GET /referendums- Get all referendumsGET /referendums/:postId- Get specific referendumPUT /referendums/:postId/:chain- Update referendumGET /referendums/:postId/actions- Get team actionsPOST /referendums/:postId/actions- Add team actionDELETE /referendums/:postId/actions- Delete team actionPOST /referendums/:postId/assign- Assign to referendumPOST /referendums/:postId/unassign- Unassign from referendumGET /referendums/:postId/comments- Get commentsPOST /referendums/:postId/comments- Add commentDELETE /comments/:commentId- Delete commentGET /referendums/:postId/agreement-summary- Get agreement summary
Admin Routes (/admin)
GET /admin/refresh-referendas- Refresh from PolkassemblyGET /admin/process-pending-transitions- Process status transitionsGET /admin/cleanup-mimir-transactions- Cleanup Mimir transactions
Mimir Routes
GET /send-to-mimir- Send ready proposals to Mimir
System Routes
GET /health- Health check
๐ Detailed Route Documentation
Auth Routes
POST /auth/web3-login
Authenticate user with Web3 wallet signature. Searches across all registered DAOs.
Request:
{
address: string; // Wallet address
signature: string; // Web3 signature
message: string; // Message that was signed
timestamp: number; // Timestamp when message was signed
}
Response (Success):
{
success: true;
token: string; // JWT token
user: {
address: string;
name: string;
network: string;
}
}
Response (403 - Not a Member):
{
success: false;
error: "Access denied: Wallet address not registered as multisig member";
details: {
address: string;
reason: string;
suggestion: string;
}
}
Notes:
- Searches all active DAOs for wallet membership
- Checks both Polkadot and Kusama multisigs
- Token contains wallet address, subsequent requests use
addDaoContextto determine DAO(s)
GET /auth/verify
Verify if authentication token is valid.
Headers: Authorization: Bearer <token>
Response:
{
success: true;
valid: true;
user: {
address: string;
name: string;
network: string;
}
}
POST /auth/logout
Logout user (client should discard token).
Headers: Authorization: Bearer <token>
Response:
{
success: true;
message: "Logged out successfully"
}
GET /auth/profile
Get current user profile.
Headers: Authorization: Bearer <token>
Response:
{
success: true;
user: {
address: string;
name: string;
network: string;
}
}
DAO Routes
GET /dao/:daoId
Get DAO information (without sensitive fields).
Headers: Authorization: Bearer <token>
Response:
{
success: true;
dao: {
id: number;
name: string;
description: string | null;
status: string | null;
created_at: string;
updated_at: string | null;
chains: Chain[]; // ['Polkadot', 'Kusama', or both]
multisig_addresses: {
polkadot: string | null;
kusama: string | null;
};
member_counts: {
polkadot: number;
kusama: number;
};
stats: {
total_referendums: number;
active_referendums: number;
voted_referendums: number;
ready_to_vote: number;
};
}
}
GET /dao/:daoId/stats
Get DAO statistics.
Headers: Authorization: Bearer <token>
Response:
{
success: true;
stats: {
total_referendums: number;
active_referendums: number;
voted_referendums: number;
ready_to_vote: number;
}
}
POST /dao/register
Register a new DAO with on-chain verification.
Headers: Authorization: Bearer <token>
Request:
{
name: string; // DAO name (3-100 chars)
description?: string; // Optional description
polkadotMultisig?: string; // Polkadot multisig address (at least one required)
kusamaMultisig?: string; // Kusama multisig address (at least one required)
walletAddress: string; // Registrant's wallet address
signature: string; // Wallet signature proving ownership
message: string; // Message that was signed (must include DAO name)
}
Response (Success):
{
success: true;
message: "DAO registered successfully";
dao: {
id: number;
name: string;
description: string | null;
chains: Chain[];
status: "active";
}
}
Response (Error):
{
success: false;
error: string;
errors?: string[]; // Validation errors
}
Error Codes:
400- Validation failed (missing fields, invalid addresses, etc.)403- Wallet not a member of provided multisig(s)409- DAO name already exists
Notes:
- Verifies wallet signature to prove ownership
- Verifies multisig(s) exist on-chain
- Verifies wallet is a member of provided multisig(s)
- Automatically generates proposer mnemonic
- Encrypts sensitive data (multisig addresses, mnemonic)
GET /dao/members
Get multisig members for the user's DAO.
Headers: Authorization: Bearer <token>
Query Parameters:
chain:Polkadot|Kusama(default:Polkadot)multisig: Optional multisig address to specify which DAO
Response:
{
success: true;
members: Array<{
address: string;
name: string;
network: string;
}>
}
GET /dao/:daoId/members/:chain
Get members for a specific DAO and chain.
Headers: Authorization: Bearer <token>
Parameters:
daoId: DAO ID (number)chain:Polkadot|Kusama
Response:
{
success: true;
members: Array<{
address: string;
name: string;
network: string;
}>
}
POST /dao/:daoId/members/:chain/refresh
Refresh the member cache for a specific DAO and chain.
Headers: Authorization: Bearer <token>
Parameters:
daoId: DAO ID (number)chain:Polkadot|Kusama
Response:
{
success: true;
message: "Member cache refreshed successfully"
}
GET /dao/:daoId/members/:chain/:walletAddress
Check if a wallet address is a member of a specific DAO.
Headers: Authorization: Bearer <token>
Parameters:
daoId: DAO ID (number)chain:Polkadot|KusamawalletAddress: Wallet address to check
Response:
{
success: true;
isMember: boolean;
member?: {
address: string;
name: string;
network: string;
}
}
GET /dao/parent
Get the parent address if the multisig is a proxy/delegate account.
Headers: Authorization: Bearer <token>
Query Parameters:
chain:Polkadot|Kusama(default:Polkadot)multisig: Optional multisig address
Response:
{
success: true;
parent: {
address: string | null;
type: string | null;
}
}
GET /dao/config
Get DAO configuration including multisig addresses and team info.
Headers: Authorization: Bearer <token>
Query Parameters:
chain:Polkadot|Kusama(default:Polkadot)multisig: Optional multisig address
Response:
{
success: true;
config: {
name: string;
team_members: TeamMember[];
required_agreements: number; // Multisig threshold
multisig_address: string; // Primary multisig
polkadot_multisig: string | null;
kusama_multisig: string | null;
}
}
GET /dao/my-assignments
Get all referendums assigned to the current user across all their DAOs.
Headers: Authorization: Bearer <token>
Response:
{
success: true;
referendums: Array<{
id: number;
post_id: number;
chain: Chain;
dao_id: number;
title: string;
description: string;
internal_status: string;
link: string;
voting_start_date?: string;
voting_end_date?: string;
created_at: string;
updated_at: string;
}>
}
GET /dao/workflow
Get all workflow data in a single request for the user's DAO.
Headers: Authorization: Bearer <token>
Query Parameters:
chain:Polkadot|Kusama(default:Polkadot)multisig: Optional multisig address
Response:
{
success: true;
data: {
needsAgreement: ProposalSummary[];
readyToVote: ProposalSummary[];
forDiscussion: ProposalSummary[];
vetoedProposals: ProposalSummary[];
}
}
Notes:
needsAgreement: Proposals needing more team member agreementsreadyToVote: Proposals with sufficient agreements, ready for MimirforDiscussion: Proposals marked for discussionvetoedProposals: Proposals with NO_WAY (veto) actions
POST /dao/sync
Trigger data synchronization with Polkassembly for the user's DAO.
Headers: Authorization: Bearer <token>
Request:
{
type?: "normal" | "deep"; // default: "normal"
}
Response:
{
success: true;
message: string;
type: "normal" | "deep";
limit: number; // 30 for normal, 100 for deep
timestamp: string;
status: "started";
}
Notes:
normal: Fetches last 30 referendumsdeep: Fetches last 100 referendums- Sync runs in background, returns immediately
POST /dao/cleanup-duplicate-actions
Clean up duplicate team actions (utility endpoint).
Response:
{
success: true;
message: "Duplicate team actions cleaned up successfully";
deletedCount: number;
duplicatesFound: number;
details: Array<{
referendum_id: number;
post_id: number;
title: string;
team_member_id: string;
}>
}
Referendum Routes
GET /referendums
Get all referendums for the user's DAO(s).
Headers: Authorization: Bearer <token>
Query Parameters:
multisig: Optional multisig address to filter by specific DAO
Response:
{
success: true;
referendums: ProposalData[]
}
GET /referendums/:postId
Get a specific referendum by post ID.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Query Parameters:
chain:Polkadot|Kusama(required)multisig: Optional multisig address
Response:
{
success: true;
referendum: ProposalData
}
Error (404):
{
success: false;
error: "Referendum {postId} not found on {chain} network"
}
PUT /referendums/:postId/:chain
Update a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)chain:Polkadot|Kusama
Request (all fields optional):
{
// Referendum fields
title?: string;
description?: string;
requested_amount_usd?: number;
origin?: string;
referendum_timeline?: string;
internal_status?: InternalStatus;
link?: string;
voting_start_date?: string;
voting_end_date?: string;
last_edited_by?: string;
public_comment?: string;
public_comment_made?: boolean;
ai_summary?: string;
reason_for_vote?: string;
reason_for_no_way?: string;
voted_link?: string;
// Voting fields
suggested_vote?: "Aye" | "Nay" | "Abstain";
final_vote?: "Aye" | "Nay" | "Abstain";
vote_executed?: boolean;
vote_executed_date?: string;
}
Response:
{
success: true;
message: "Referendum updated successfully"
}
Notes:
- Status transitions are validated
- Only assigned users can change status (except special statuses)
- Suggested vote updates may trigger automatic status changes
- Cannot update
dao_idfield
GET /referendums/:postId/actions
Get team actions for a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Query Parameters:
chain:Polkadot|Kusama(required)
Response:
{
success: true;
actions: Array<{
id: number;
team_member_id: string;
team_member_name: string;
role_type: string;
reason?: string;
created_at: string;
updated_at?: string;
wallet_address: string;
network: string;
}>
}
POST /referendums/:postId/actions
Add a team action to a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Request:
{
chain: "Polkadot" | "Kusama"; // Required
action: "agree" | "to_be_discussed" | "no_way" | "recuse"; // Required
reason?: string; // Optional
}
Response:
{
success: true;
message: "Team action added successfully"
}
Notes:
- If action already exists, it will be updated with new reason
- May trigger automatic status transitions (e.g., to ReadyToVote)
DELETE /referendums/:postId/actions
Delete a team action from a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Request:
{
chain: "Polkadot" | "Kusama"; // Required
action: "agree" | "to_be_discussed" | "no_way" | "recuse"; // Required
}
Response:
{
success: true;
message: "Team action removed successfully"
}
POST /referendums/:postId/assign
Assign the current user as the responsible person for a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Request:
{
chain: "Polkadot" | "Kusama"; // Required
}
Response:
{
success: true;
message: "Assigned successfully"
}
Notes:
- Only one user can be assigned at a time
- Returns 400 if already assigned to another user
POST /referendums/:postId/unassign
Unassign the responsible person from a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Request:
{
chain: "Polkadot" | "Kusama"; // Required
unassignNote?: string; // Optional note
}
Response:
{
success: true;
message: "Unassigned successfully"
}
Notes:
- Only the assigned user can unassign themselves
- Resets referendum state
GET /referendums/:postId/comments
Get comments for a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Query Parameters:
chain:Polkadot|Kusama(required)
Response:
{
success: true;
comments: Array<{
id: number;
content: string;
user_address: string;
user_name: string;
created_at: string;
updated_at?: string;
}>
}
POST /referendums/:postId/comments
Add a comment to a referendum.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Request:
{
chain: "Polkadot" | "Kusama"; // Required
content: string; // Required, non-empty
}
Response:
{
success: true;
message: "Comment added successfully";
comment: {
id: number;
content: string;
user_address: string;
created_at: string;
}
}
DELETE /comments/:commentId
Delete a comment (only by the author).
Headers: Authorization: Bearer <token>
Parameters:
commentId: Comment ID (number)
Response:
{
success: true;
message: "Comment deleted successfully"
}
Notes:
- Only the comment author can delete their own comments
GET /referendums/:postId/agreement-summary
Get agreement summary during discussion period.
Headers: Authorization: Bearer <token>
Parameters:
postId: Polkassembly post ID (number)
Query Parameters:
chain:Polkadot|Kusama(required)
Response:
{
success: true;
summary: {
agreed_members: Array<{ address: string; name: string }>;
pending_members: Array<{ address: string; name: string }>;
recused_members: Array<{ address: string; name: string }>;
to_be_discussed_members: Array<{ address: string; name: string }>;
vetoed: boolean;
veto_by: string | null;
veto_reason: string | null;
}
}
Admin Routes
GET /admin/refresh-referendas
Manually trigger refresh from Polkassembly (refreshes all active DAOs).
Query Parameters:
limit: Number of referendums to fetch (default: 30)
Response:
{
message: "Referenda refresh started in background with limit {limit}";
timestamp: string;
limit: number;
status: "started";
}
Notes:
- Runs in background, returns immediately
- Refreshes for all active DAOs
GET /admin/process-pending-transitions
Process pending status transitions (failsafe).
Response:
{
message: "Pending transitions processed successfully";
timestamp: string;
processed: number;
transitioned: number;
details: Array<{
referendumId: number;
postId: number;
chain: string;
oldStatus: string;
newStatus: string;
}>
}
GET /admin/cleanup-mimir-transactions
Clean up stale Mimir transactions.
Query Parameters:
days: Optional, cleanup transactions older than N days
Response:
{
message: "Mimir transaction cleanup completed successfully";
timestamp: string;
mode: string;
staleTransactionsFound: number;
transactionsCleaned: number;
}
Mimir Routes
GET /send-to-mimir
Send ready-to-vote proposals to Mimir for all active DAOs.
Response:
{
success: true;
message: "Successfully sent referendas to Mimir";
timestamp: string;
}
System Routes
GET /health
Health check endpoint.
Response:
{
status: "healthy";
timestamp: string;
uptime: number; // Process uptime in seconds
}
Common Types
type Chain = 'Polkadot' | 'Kusama';
type InternalStatus =
| 'Not started'
| 'Considering'
| 'Ready for approval'
| 'Waiting for agreement'
| 'Reconsidering'
| 'Ready to vote'
| 'Voted Aye'
| 'Voted Nay'
| 'Voted Abstain'
| 'Not Voted';
interface TeamMember {
address: string;
name: string;
network: string;
}
interface ProposalData {
id: number;
post_id: number;
chain: Chain;
dao_id: number;
title: string;
description?: string;
requested_amount_usd?: number;
origin?: string;
referendum_timeline?: string;
internal_status: string;
link?: string;
voting_start_date?: string;
voting_end_date?: string;
created_at: string;
updated_at?: string;
suggested_vote?: string;
final_vote?: string;
vote_executed?: boolean;
}
interface ProposalSummary {
id: number;
post_id: number;
chain: Chain;
dao_id: number;
title: string;
description: string;
internal_status: string;
team_actions?: any[];
}
Error Responses
All error responses follow this format:
{
success: false;
error: string;
details?: any; // Optional additional error details
}
HTTP Status Codes
200- Success201- Created (new resource)400- Bad Request (invalid parameters)401- Unauthorized (missing/invalid token)403- Forbidden (insufficient permissions)404- Not Found (resource doesn't exist)409- Conflict (duplicate resource)500- Internal Server Error
Multi-DAO Query Parameters
Many endpoints support the following query parameters for multi-DAO contexts:
multisig: Multisig address to identify specific DAOchain:Polkadot|Kusama(default varies by endpoint)
Example:
GET /dao/members?chain=Polkadot&multisig=15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5
Alternatively, use the X-Multisig-Address header:
GET /dao/members?chain=Polkadot
X-Multisig-Address: 15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5
Rate Limiting
API routes are protected by rate limiting:
- Default: 100 requests per 15 minutes per IP
- Auth routes: 5 requests per 15 minutes per IP
- Admin routes: No rate limiting (consider adding authentication)
Changelog
Version 2.0.0 (Multi-DAO Support)
- โ Added DAO registration endpoint
- โ Added DAO-specific member endpoints
- โ Added DAO stats endpoint
- โ Updated all routes to support multiple DAOs
- โ Added multisig query parameter support
- โ Removed environment variable dependencies
- โ Added on-chain verification for DAO registration
- โ Updated authentication to search across all DAOs
Version 1.0.0 (Initial Release)
- Initial API implementation with single DAO support