> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fucksornot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Voting

> How the FoN voting system works

## Overview

Voting is central to FoN. Users vote on whether content "fucks" or "does not fuck", and these votes determine what rises to the top.

## Vote Types

### Authenticated Voting

Registered users can cast votes that carry full weight:

```bash theme={null}
curl -X POST https://fucksornot.com/api/vote \
  -H "Cookie: auth-token=YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "uploadId": "upload-uuid",
    "vote": "fucks"
  }'
```

**Vote options:**

* `fucks` - This content fucks
* `does_not_fuck` - This content does not fuck

### Anonymous Voting

Unauthenticated users can also vote using browser fingerprinting:

```bash theme={null}
curl -X POST https://fucksornot.com/api/vote/anonymous \
  -H "Content-Type: application/json" \
  -d '{
    "uploadId": "upload-uuid",
    "vote": "upvote",
    "clientFingerprint": "fingerprint-hash"
  }'
```

**Anonymous vote options:**

* `upvote` - Equivalent to "fucks"
* `downvote` - Equivalent to "does not fuck"

<Note>
  Anonymous votes use fingerprinting to prevent vote manipulation. Stricter rate limits apply.
</Note>

## Vote Response

Successful votes return the vote record:

```json theme={null}
{
  "vote": {
    "voteId": "vote-uuid",
    "userId": "user-uuid",
    "uploadId": "upload-uuid",
    "vote": "fucks"
  }
}
```

## Getting Your Vote on an Upload

Check whether you've voted on a specific upload by passing its `uploadId` as a query parameter:

```bash theme={null}
curl "https://fucksornot.com/api/vote?uploadId=upload-uuid" \
  -H "Cookie: auth-token=YOUR_JWT_TOKEN"
```

`uploadId` is required — if it's omitted, the request returns a `400` error (`"Upload ID required"`).

Response:

```json theme={null}
{
  "vote": "fucks"
}
```

If you haven't voted on that upload, `vote` is `null`:

```json theme={null}
{
  "vote": null
}
```

This endpoint returns the current user's vote status for a single upload — it does not return a list of all votes the user has cast.

## Vote Counts

Each upload tracks vote counts:

| Field            | Description                     |
| ---------------- | ------------------------------- |
| `upvotes`        | Number of "fucks" votes         |
| `downvote_count` | Number of "does not fuck" votes |

These are returned in upload responses:

```json theme={null}
{
  "id": "upload-uuid",
  "description": "Cool gadget",
  "upvotes": 42,
  "downvote_count": 5
}
```

## Changing Votes

Users can change their vote by submitting a new vote for the same upload. The previous vote is replaced.

## Rate Limits

Voting is rate limited to prevent abuse:

| Vote Type     | Limit    | Window   |
| ------------- | -------- | -------- |
| Authenticated | 30 votes | 1 minute |
| Anonymous     | 10 votes | 1 minute |

## Shadow Banning

Admins can shadow ban users. Shadow banned users:

* Can still vote normally
* Their votes are recorded
* **Their votes don't affect counts**

This allows moderation without alerting bad actors.

## Best Practices

<AccordionGroup>
  <Accordion title="Cache vote state locally">
    Track which uploads the user has voted on to prevent redundant API calls.
  </Accordion>

  <Accordion title="Handle rate limits gracefully">
    When rate limited, inform the user and wait before retrying.
  </Accordion>

  <Accordion title="Use authenticated voting when possible">
    Authenticated votes are more trusted and less strictly rate limited.
  </Accordion>
</AccordionGroup>
