Skip to content

Votes API

!!! info "TL;DR" Vote on submissions to show support. Toggle pattern: POST to add vote, DELETE to remove. One vote per user per submission.

Vote on Submission

Add a vote to a submission. If the user has already voted, this operation is idempotent (no error).

POST /api/submissions/:id/vote

Authentication: Required

Example Request:

curl -X POST https://canviq.app/api/submissions/550e8400-e29b-41d4-a716-446655440000/vote \
  -H "Authorization: Bearer your-api-key"

Example Response:

{
  "data": {
    "submission_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "uuid",
    "vote_count": 43,
    "created_at": "2026-02-10T15:30:00Z"
  }
}

Side Effects:

  • Increments vote_count on the submission (via database trigger)
  • Creates a follow relationship (user follows the submission)
  • Sends notification to submission author

Remove Vote

Remove a vote from a submission.

DELETE /api/submissions/:id/vote

Authentication: Required

Example Request:

curl -X DELETE https://canviq.app/api/submissions/550e8400-e29b-41d4-a716-446655440000/vote \
  -H "Authorization: Bearer your-api-key"

Example Response:

{
  "data": {
    "submission_id": "550e8400-e29b-41d4-a716-446655440000",
    "vote_count": 42
  }
}

Side Effects:

  • Decrements vote_count on the submission (via database trigger)
  • Does not remove the follow relationship (user continues following)

Get User's Votes

Get all submissions the authenticated user has voted for.

GET /api/votes

Authentication: Required

Query Parameters:

Parameter Type Description
page number Page number (default: 1)
limit number Items per page (default: 50)

Example Request:

curl https://canviq.app/api/votes \
  -H "Authorization: Bearer your-api-key"

Example Response:

{
  "data": [
    {
      "submission_id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Dark mode support",
      "status": "planned",
      "vote_count": 42,
      "voted_at": "2026-01-20T10:00:00Z"
    },
    {
      "submission_id": "550e8400-e29b-41d4-a716-446655440001",
      "title": "API access",
      "status": "in_progress",
      "vote_count": 67,
      "voted_at": "2026-01-18T14:30:00Z"
    }
  ],
  "meta": {
    "total": 15
  }
}

Get Submission Voters

Get users who voted for a submission (admin only).

GET /api/submissions/:id/voters

Authentication: Required (admin)

Query Parameters:

Parameter Type Description
page number Page number
limit number Items per page

Example Request:

curl https://canviq.app/api/submissions/550e8400-e29b-41d4-a716-446655440000/voters \
  -H "Authorization: Bearer admin-api-key"

Example Response:

{
  "data": [
    {
      "user_id": "uuid",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "voted_at": "2026-02-10T10:00:00Z"
    }
  ],
  "meta": {
    "total": 42
  }
}

Realtime Vote Updates

Subscribe to realtime vote count changes via Supabase Realtime:

import { createBrowserClient } from '@/lib/supabase/client'

const supabase = createBrowserClient()

const channel = supabase
  .channel(`votes:submission=${submissionId}`)
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'votes',
      filter: `submission_id=eq.${submissionId}`,
    },
    (payload) => {
      console.log('Vote change:', payload)
      // Update UI with new vote count
    }
  )
  .subscribe()

The UI updates in real time as other users vote.

Rate Limits

Endpoint Limit Window
POST /vote 30 1 hour
DELETE /vote 30 1 hour
GET /votes 100 1 minute

Voting is rate-limited to prevent abuse. If a user votes excessively, they'll receive a 429 Too Many Requests response.

Business Rules

  • One vote per user per submission
  • Voting automatically creates a follow relationship
  • Removing a vote does not remove the follow (user still gets updates)
  • Authors automatically vote for their own submissions on creation
  • Deleted submissions cascade delete all votes

What's Next