Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MatthewSabia1/SubPirate-Pro/llms.txt

Use this file to discover all available pages before exploring further.

Campaign Management

Campaigns allow you to automate Reddit posts across multiple accounts and subreddits. Each campaign has:
  • Content versions with post title, body, and media
  • Reddit account selection for posting
  • Subreddit targets to post in
  • Scheduling configuration with daily post targets and timezone
  • Team collaboration with role-based permissions

Authentication

All campaign endpoints require authentication via Bearer token:
Authorization: Bearer YOUR_SUPABASE_JWT

Role-Based Access Control

Campaigns support four permission levels:
RolePermissions
OwnerFull access (campaign creator, assigned via user_id)
AdminManage members, edit campaign, run campaigns, view all data
EditorEdit campaign settings, run campaigns, view data
ViewerRead-only access to campaign data
Permissions are stored in the campaign_members table. The campaign owner (via campaigns.user_id) automatically has admin rights.

GET /api/campaigns

This endpoint uses Supabase client-side queries. Access campaigns via:
const { data, error } = await supabase
  .from('campaigns')
  .select('*')
  .order('created_at', { ascending: false });
List all campaigns the authenticated user has access to. Row-level security (RLS) policies automatically filter campaigns based on:
  • Campaigns owned by the user (user_id matches)
  • Campaigns where the user is a member (campaign_members table)

Response Schema

campaigns
array
Array of campaign objects

Example Request

import { supabase } from './supabase';

const { data: campaigns, error } = await supabase
  .from('campaigns')
  .select('*')
  .order('created_at', { ascending: false });

if (error) {
  console.error('Failed to load campaigns:', error.message);
} else {
  console.log(`Loaded ${campaigns.length} campaigns`);
}

POST /api/campaigns

This endpoint uses Supabase client-side mutations:
const { data, error } = await supabase
  .from('campaigns')
  .insert({ name, description, ... })
  .select()
  .single();
Create a new campaign. The authenticated user becomes the campaign owner.

Request Body

name
string
required
Campaign name (max 255 characters)
description
string
Optional campaign description
objective
string
Campaign objective or goal
status
string
default:"draft"
Initial status: draft, active, paused, or completed
start_date
string
ISO 8601 date (YYYY-MM-DD) when campaign should start
end_date
string
ISO 8601 date (YYYY-MM-DD) when campaign should end
daily_post_target
number
default:"1"
Number of posts to attempt per day (must be >= 1)
timezone
string
default:"UTC"
IANA timezone identifier (e.g., America/New_York)
jitter_seconds
number
default:"0"
Random time offset (±seconds) for scheduled runs
carry_over_enabled
boolean
default:"false"
If true, shortfall from previous days carries over

Response

Returns the created campaign object (see GET response schema above).

Example Request

const { data: campaign, error } = await supabase
  .from('campaigns')
  .insert({
    name: 'Product Launch Campaign',
    description: 'Promote our new SaaS product across tech subreddits',
    objective: 'Drive 1000 signups in Q1',
    status: 'draft',
    start_date: '2026-03-15',
    end_date: '2026-06-30',
    daily_post_target: 5,
    timezone: 'America/Los_Angeles',
    carry_over_enabled: true,
  })
  .select()
  .single();

if (error) {
  console.error('Failed to create campaign:', error.message);
} else {
  console.log('Campaign created:', campaign.id);
}
curl -X POST https://your-api.example.com/api/campaigns \
  -H "Authorization: Bearer YOUR_SUPABASE_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Product Launch Campaign",
    "description": "Promote our new SaaS product",
    "status": "draft",
    "daily_post_target": 5,
    "timezone": "America/Los_Angeles"
  }'

GET /api/campaigns/:id

Access via Supabase:
const { data, error } = await supabase
  .from('campaigns')
  .select('*')
  .eq('id', campaignId)
  .single();
Get a specific campaign by ID. Requires at least Viewer role.

Path Parameters

id
string
required
Campaign UUID

Response

Returns a single campaign object (see GET /api/campaigns schema).

Errors

  • 404: Campaign not found or user lacks access
  • 403: User does not have permission to view this campaign

PATCH /api/campaigns/:id

Update via Supabase:
const { data, error } = await supabase
  .from('campaigns')
  .update({ name, status, ... })
  .eq('id', campaignId)
  .select()
  .single();
Update a campaign. Requires at least Editor role.

Path Parameters

id
string
required
Campaign UUID

Request Body

All fields are optional. Only provided fields will be updated.
name
string
Campaign name
description
string
Campaign description
objective
string
Campaign objective
status
string
Status: draft, active, paused, or completed
start_date
string
Start date (YYYY-MM-DD)
end_date
string
End date (YYYY-MM-DD)
daily_post_target
number
Daily post target (>= 1)
timezone
string
IANA timezone identifier
jitter_seconds
number
Random time offset for scheduling
carry_over_enabled
boolean
Enable shortfall carry-over

Response

Returns the updated campaign object.

Example Request

const { data: campaign, error } = await supabase
  .from('campaigns')
  .update({
    status: 'active',
    daily_post_target: 10,
  })
  .eq('id', campaignId)
  .select()
  .single();

DELETE /api/campaigns/:id

Delete via Supabase:
const { error } = await supabase
  .from('campaigns')
  .delete()
  .eq('id', campaignId);
Delete a campaign. Requires Owner or Admin role. This will cascade-delete:
  • All campaign runs and attempts
  • Campaign content versions
  • Campaign members and invites
  • Reddit account and subreddit associations

Path Parameters

id
string
required
Campaign UUID

Response

  • 204: Campaign deleted successfully
  • 403: User does not have permission to delete this campaign
  • 404: Campaign not found

Example Request

const { error } = await supabase
  .from('campaigns')
  .delete()
  .eq('id', campaignId);

if (error) {
  console.error('Failed to delete campaign:', error.message);
} else {
  console.log('Campaign deleted');
}