API Reference

Complete API documentation and SDK reference for VOTR integration

πŸ“š Interactive API Documentation

For a complete interactive API documentation with live testing capabilities, visit our Swagger documentation:

πŸ”— VOTR API Swagger Documentation

The Swagger documentation provides:

  • Interactive API endpoint testing
  • Detailed request/response schemas
  • Authentication examples
  • Real-time API exploration

OAuth Token

API Endpoint

POST https://api-dev.govotr.com/api/v1/oauth/token  (Development)
POST https://api.govotr.com/api/v1/oauth/token      (Production)

Request Body

{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET"
}

Response

{
  "access_token": "eyJraWQiOiJjRFNKN2VsazhoZzdLcEVoakJLcFRNSHRZMlN2eXFTWEwyUUdJTHNNQm5JPSIsImFsZyI6IlJTMjU2In0...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Important Notes

API Response Format:

  • The API returns the voting URL in response.data.url
  • Response uses status (boolean) instead of success
  • The URL contains a JWT token as a query parameter

Usage in Code:

// Frontend calls YOUR backend API (not VOTR API directly)
// Option 1: Using email
const response = await fetch("/api/voting/generate-url", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${userAuthToken}`, // Your app's user token
  },
  body: JSON.stringify({
    email: userEmail,
    eventId: eventId,
  }),
})

// Option 2: Using account number
const response = await fetch("/api/voting/generate-url", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${userAuthToken}`, // Your app's user token
  },
  body: JSON.stringify({
    accountNo: userAccountNo,
    eventId: eventId,
  }),
})

const data = await response.json()

if (data.status) {
  const votingUrl = data.data.url // This is what you pass to SDK
  setVotingUrl(votingUrl)
}

Voting URL

Get Ballot by Event and Account:

GET https://api-dev.govotr.com/api/v1/events/:eventId/ballot  (Development)
GET https://api.govotr.com/api/v1/events/:eventId/ballot      (Production)

Path Parameters

Parameter Type Required Description
eventId string βœ… 24-character hexadecimal event ID (ObjectId)

Query Parameters

Parameter Type Required Description
accountNo string βœ… Account number of the shareholder
brokerId string βœ… 24-character hexadecimal broker ID

Headers

Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

Requirements

  • Valid OAuth 2.0 access token with appropriate scope
  • Account number and broker ID must be provided (both required)
  • Account must be registered as a shareholder for the specified event
  • Event must be in active voting period
  • Voting must be live for the event
  • Event must be a Proxy event type

Success Response

{
  "message": "Data retrieved successfully",
  "status": true,
  "data": {
    "url": "https://vote.govotr.com/public-voting-screen?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Response Fields:

Field Type Description
message string Success message
status boolean Request status (true for success)
data.url string Voting URL with authentication token

Token Validity:

  • Tokens are valid for 1 hour
  • Should be used within that timeframe
  • After expiration, a new token must be requested

Error Responses

400 - Bad Request (Validation Error):

{
  "status": false,
  "error": "Account number is required"
}
{
  "status": false,
  "error": "Broker ID is required"
}
{
  "status": false,
  "error": "Invalid event ID format"
}

401 - Unauthorized:

{
  "status": false,
  "error": "Invalid or missing authentication token"
}

403 - Forbidden:

{
  "status": false,
  "error": "You do not have permission to access this resource"
}

404 - Not Found:

{
  "status": false,
  "error": "Event not found"
}
{
  "status": false,
  "error": "Account not found for this event"
}
{
  "status": false,
  "error": "Ballot not found"
}

423 - Locked (Resource Unavailable):

{
  "status": false,
  "error": "Voting URL Not Active"
}
{
  "status": false,
  "error": "Voting period has not started yet"
}
{
  "status": false,
  "error": "Event is not a Proxy event type"
}

429 - Too Many Requests:

{
  "status": false,
  "error": "Rate limit exceeded. Please try again later"
}

500 - Internal Server Error:

{
  "status": false,
  "error": "Internal server error"
}

Usage Example

cURL:

curl -X GET "https://api-dev.govotr.com/api/v1/events/64f8a1b2c3d4e5f6a7b8c9d0/ballot?accountNo=ACC123456&brokerId=681a3f923b07005fdc5b5417" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json"

JavaScript/Node.js:

async function getVotingUrl(accessToken, eventId, accountNo, brokerId) {
  const url = `https://api-dev.govotr.com/api/v1/events/${eventId}/ballot?accountNo=${accountNo}&brokerId=${brokerId}`

  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
    })

    const data = await response.json()

    if (data.status) {
      console.log("Voting URL:", data.data.url)
      return data.data.url
    } else {
      console.error("Error:", data.error)
      return null
    }
  } catch (error) {
    console.error("Network error:", error)
    return null
  }
}

// Usage
const votingUrl = await getVotingUrl(
  "your_access_token",
  "64f8a1b2c3d4e5f6a7b8c9d0",
  "ACC123456",
  "681a3f923b07005fdc5b5417"
)

Python:

import requests

def get_voting_url(access_token, event_id, account_no, broker_id):
    url = f"https://api-dev.govotr.com/api/v1/events/{event_id}/ballot"

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }

    params = {
        "accountNo": account_no,
        "brokerId": broker_id
    }

    try:
        response = requests.get(url, headers=headers, params=params)
        data = response.json()

        if response.status_code == 200 and data.get('status'):
            print(f"Voting URL: {data['data']['url']}")
            return data['data']['url']
        else:
            print(f"Error: {data.get('error', 'Unknown error')}")
            return None

    except requests.exceptions.RequestException as e:
        print(f"Network error: {e}")
        return None

# Usage
voting_url = get_voting_url(
    'your_access_token',
    '64f8a1b2c3d4e5f6a7b8c9d0',
    'ACC123456',
    '681a3f923b07005fdc5b5417'
)

Deprecated Endpoint ⚠️

⚠️ DEPRECATED: The /ballot/by-email endpoint is deprecated and will be removed in a future version. Please migrate to the new GET /api/v1/events/:eventId/ballot endpoint.

POST https://api-dev.govotr.com/api/v1/ballot/by-email  (Development) - DEPRECATED
POST https://api.govotr.com/api/v1/ballot/by-email      (Production) - DEPRECATED

Headers

Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

Request Body (Deprecated Endpoint)

Option 1: Using Email

{
  "email": "john.doe@example.com",
  "eventId": "64f8a1b2c3d4e5f6a7b8c9d0"
}

Option 2: Using Account Number

{
  "accountNo": "ACC123456789",
  "eventId": "64f8a1b2c3d4e5f6a7b8c9d0"
}

Option 3: Using Control Number (Deprecated)

{
  "controlNo": "CTRL123456",
  "eventId": "64f8a1b2c3d4e5f6a7b8c9d0"
}

Note: controlNo references have been removed from all new endpoints. This option is only available in the deprecated endpoint.

Important Notes:

  • Either email, accountNo, or controlNo must be provided (only one, not multiple)
  • The provided identifier must be registered as a shareholder for the specified event
  • Token expires after 1 hour
  • Event must be live/active for voting

Response (Deprecated Endpoint)

Success Response:

{
  "message": "Data retrieved successfully",
  "status": true,
  "data": {
    "url": "http://localhost:5173/public-voting-screen?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Responses (Deprecated Endpoint)

Shareholder not found by email:

{
  "status": false,
  "error": "email: john.doe@example.com not found for this event"
}

Shareholder not found by account number:

{
  "status": false,
  "error": "account number: ACC123456 not found for this event"
}

Shareholder not found by control number:

{
  "status": false,
  "error": "control number: CTRL123456 not found for this event"
}

Voting not active:

{
  "status": false,
  "error": "Voting URL Not Active"
}

Voting period has ended:

{
  "status": false,
  "error": "Voting period has ended"
}

πŸ“‹ For complete backend implementation examples, please refer to: Sample Repository


SDK Component

βœ… This step is implemented in your React Native frontend.

Basic Usage

Once you have the voting URL, pass it to the SDK component:

import React, { useState } from "react"
import { StyleSheet, Alert } from "react-native"
import { VoteNowButton } from "@govotr/vote-react-native"

export default function VotingComponent() {
  const [votingUrl, setVotingUrl] = useState<string>("")
  const [isLoading, setIsLoading] = useState<boolean>(false)

  const handleVoteClick = async () => {
    setIsLoading(true)

    try {
      // Call YOUR backend API to get voting URL
      // You can use either email or accountNo
      const requestBody = userEmail
        ? { email: userEmail, eventId: eventId }
        : { accountNo: userAccountNo, eventId: eventId }

      const response = await fetch("https://your-api.com/vote", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(requestBody),
      })

      const data = await response.json()

      if (data.status && data.data.url) {
        setVotingUrl(data.data.url)
      } else {
        Alert.alert("Error", "Failed to get voting URL")
      }
    } catch (error) {
      console.error("Vote API error:", error)
    } finally {
      setIsLoading(false)
    }
  }

  const styles = StyleSheet.create({
    voteButton: {
      backgroundColor: "#5263FF",
      borderRadius: 12,
      alignItems: "center",
    },
    voteButtonText: {
      color: "white",
      fontSize: 18,
      fontWeight: "bold",
    },
  })

  return (
    <VoteNowButton
      URL={votingUrl}
      label="Vote Now"
      buttonStyle={styles.voteButton}
      textStyle={styles.voteButtonText}
      onPress={handleVoteClick}
      isLoading={isLoading}
      onSuccess={() => {
        setVotingUrl("") // Clear URL after successful vote
      }}
      onBack={() => {
        setVotingUrl("") // Clear URL when user goes back
      }}
      onError={(error) => {
        console.log(error)
        Alert.alert("Error", "Voting failed")
      }}
    />
  )
}

Advanced Usage with Custom Styling

<VoteNowButton
  URL={votingUrl}
  label="Cast Your Vote"
  buttonStyle={{
    backgroundColor: "#007AFF",
    borderRadius: 8,
    paddingVertical: 12,
    paddingHorizontal: 24,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  }}
  textStyle={{
    color: "white",
    fontSize: 16,
    fontWeight: "bold",
  }}
  onSuccess={() => {
    Alert.alert("Success", "Your vote has been recorded!")
  }}
  onError={(error) => {
    Alert.alert("Error", `Voting failed: ${error}`)
  }}
/>

Complete Working Example

Here’s a full working example showing exactly how to use the VoteNowButton SDK:

import React, { useState } from "react"
import { View, Alert, StyleSheet, Text } from "react-native"
import { VoteNowButton } from "@govotr/vote-react-native"

export default function VotingScreen() {
  const [votingUrl, setVotingUrl] = useState<string>("")
  const [isLoading, setIsLoading] = useState<boolean>(false)

  // Function to handle vote API call
  const handleVoteClick = async () => {
    setIsLoading(true)

    try {
      // Call YOUR backend API to get voting URL
      // Prepare request body with either email or accountNo
      const requestBody = {
        eventId: "64f8a1b2c3d4e5f6a7b8c9d0", // Replace with actual eventId
        ...(userEmail ? { email: userEmail } : { accountNo: userAccountNo }),
      }

      const response = await fetch("http://your-api-url.com/vote", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody),
      })

      const data = await response.json()

      if (data.status && data.data.url) {
        setVotingUrl(data.data.url)
        console.log("Vote URL received:", data.data.url)
      } else {
        Alert.alert("Error", data.error || "Failed to get voting URL")
      }
    } catch (error) {
      console.error("Vote API error:", error)
      Alert.alert("Error", "Unable to start voting. Please try again.")
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Annual General Meeting 2024</Text>
      <Text style={styles.subtitle}>Shareholder Voting</Text>

      <VoteNowButton
        URL={votingUrl ? votingUrl : ""}
        label="Vote Now"
        buttonStyle={styles.voteButton}
        textStyle={styles.voteButtonText}
        onPress={handleVoteClick}
        isLoading={isLoading}
        onSuccess={() => {
          setVotingUrl("") // Clear URL after successful vote
          Alert.alert("Success", "Your vote has been recorded successfully!")
        }}
        onBack={() => {
          setVotingUrl("") // Clear URL when user goes back
          console.log("User closed voting modal")
        }}
        onError={(error) => {
          console.log(error)
          Alert.alert(
            "Voting Error",
            "An error occurred while voting. Please try again."
          )
        }}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: "center",
    alignItems: "center",
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    textAlign: "center",
    marginBottom: 10,
  },
  subtitle: {
    fontSize: 18,
    textAlign: "center",
    marginBottom: 30,
    color: "#666",
  },
  voteButton: {
    backgroundColor: "#5263FF",
    borderRadius: 12,
    alignItems: "center",
    minWidth: 200,
  },
  voteButtonText: {
    color: "white",
    fontSize: 18,
    fontWeight: "bold",
    textAlign: "center",
  },
  successMessage: {
    fontSize: 18,
    color: "green",
    textAlign: "center",
    fontWeight: "bold",
  },
})

SDK Props Reference

VoteNowButton Props

Prop Type Required Description
URL string βœ… The voting URL obtained from VOTR API
label string ❌ Button text (default: β€œVote Now”)
buttonStyle ViewStyle ❌ Custom styling for the button
textStyle TextStyle ❌ Custom styling for the button text
isDisabled boolean ❌ Whether the button is disabled
onPress () => void ❌ Called when button is pressed
onSuccess () => void ❌ Called when voting completes successfully
onError (error: string) => void ❌ Called when an error occurs
onBack () => void ❌ Called when user closes the voting modal

Example with All Props

<VoteNowButton
  URL={votingUrl}
  label="Submit Your Vote"
  buttonStyle={{
    backgroundColor: "#28a745",
    borderRadius: 12,
    paddingVertical: 16,
    paddingHorizontal: 32,
    elevation: 3,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
  }}
  textStyle={{
    color: "#ffffff",
    fontSize: 18,
    fontWeight: "600",
    textAlign: "center",
  }}
  isDisabled={!votingUrl}
  onPress={() => console.log("Vote button pressed")}
  onSuccess={() => {
    Alert.alert("Success", "Vote submitted successfully!")
    navigation.goBack()
  }}
  onError={(error) => {
    console.error("Voting error:", error)
    Alert.alert("Error", "Failed to submit vote. Please try again.")
  }}
  onBack={() => {
    console.log("User cancelled voting")
  }}
/>

Error Handling

Common Error Scenarios

<VoteNowButton
  URL={votingUrl}
  label="Vote Now"
  onError={(error) => {
    let userMessage = "An error occurred while voting. Please try again."

    if (error.includes("network")) {
      userMessage =
        "Network connection lost. Please check your internet connection."
    } else if (error.includes("timeout")) {
      userMessage = "Voting session timed out. Please try again."
    } else if (error.includes("permission")) {
      userMessage = "You do not have permission to access this voting session."
    }

    Alert.alert("Voting Error", userMessage)
    setVotingUrl("") // Clear URL to close modal
  }}
/>

Test Data

Download Test Data

For your convenience, we provide test data that you can use to test your integration:

πŸ“₯ Download Test Data PDF

Sample Test Data

Use the following test data to verify your API integration:

Test Data with Email:

EventId Email Expected Response
68da919c6837407211b64db5 lindsey@govotr.com Vote now Url Generated successfully
68cbe3fc76a057049a2cf7b4 maham@govotr.com Vote now Url Generated successfully
68cbe3fc76a057049a2cf7b4 hamza@govotr.com Email not found for this event
68cbe3fc76a057049a2cf7b4 affan@govotr.com Vote now Url Generated successfully
68cbe68876a057049a2cf945 maham@govotr.com Vote now Url Generated successfully
68cbe68876a057049a2cf945 hamza@govotr.com Email not found for this event
68cbe68876a057049a2cf945 affan@govotr.com Vote now Url Generated successfully
68cbe7dd76a057049a2cfa79 Any email Voting period has ended

Test Data with Account Number:

EventId Account Number Expected Response
68da919c6837407211b64db5 ACC001234567890 Vote now Url Generated successfully
68cbe3fc76a057049a2cf7b4 34646800 Vote now Url Generated successfully
68cbe3fc76a057049a2cf7b4 34646819 Account not found for this event
68cbe3fc76a057049a2cf7b4 64410242 Vote now Url Generated successfully
68cbe68876a057049a2cf945 79818786 Vote now Url Generated successfully
68cbe68876a057049a2cf945 12450920 Account not found for this event
68cbe68876a057049a2cf945 24449208 Vote now Url Generated successfully
68cbe7dd76a057049a2cfa79 Any account number Voting period has ended

How to Use Test Data

  1. Copy an EventId from the table above
  2. Use the corresponding email, account number, or control number for testing
  3. Call the Voting URL API with these parameters
  4. Verify the response matches the expected result

Example API Call with Email:

curl -X POST "https://api-dev.govotr.com/api/v1/ballot/by-email" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "68cbe3fc76a057049a2cf7b4",
    "email": "maham@govotr.com"
  }'

Example API Call with Account Number:

curl -X POST "https://api-dev.govotr.com/api/v1/ballot/by-email" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "68cbe3fc76a057049a2cf7b4",
    "accountNo": "34646800"
  }'

Example API Call with Control Number:

curl -X POST "https://api-dev.govotr.com/api/v1/ballot/by-email" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "68cbe3fc76a057049a2cf7b4",
    "controlNo": "CTRL002"
  }'

Expected Response:

{
  "message": "Data retrieved successfully",
  "status": true,
  "data": {
    "url": "https://vote.govotr.com/public-voting-screen?token=..."
  }
}

Next Step: Troubleshooting Guide for common issues and solutions.