Skip to main content

Overview

This guide is written for autonomous agents (bots, LLMs, automated pipelines) using the ReelMirror API. It answers two questions: what can a bot do without human intervention, and what requires a human to set up first?

Autonomous Account Creation

An agent can bootstrap a ReelMirror account entirely on its own — no human required.

Step 1: Sign up

curl -X POST "https://reelmirror.com/api/v1/auth/signup" \
  -H "Content-Type: application/json" \
  -d '{"email": "agent@example.com", "password": "<strong-random-password>"}'
# → {"status": "pending_confirmation", "user_id": "..."}
The agent must store the password securely. The account starts with **0balancethe0 balance** — the 5 welcome bonus is deferred until a payment method is attached. Rate limit: 5 signups per IP per 24 hours. No auth required.

Step 2: Confirm email

Supabase sends a confirmation link to the provided email. The agent reads the link from its inbox and follows it. Once confirmed, email_confirmed_at is set on the user.

Step 3: Get an API key

Use the Supabase JWT from the email confirmation callback to exchange for a permanent API key:
curl -X POST "https://reelmirror.com/api/v1/api-keys" \
  -H "Authorization: Bearer <supabase_jwt>" \
  -H "Content-Type: application/json" \
  -d '{"name": "agent-key", "scopes": ["personas:read", "personas:write", "sources:read", "sources:write", "content:read", "content:write", "sync:write", "uploads:write", "publishing:read", "publishing:write", "billing:read", "billing:write"]}'
# → {"id": "...", "key": "rm_xxx", "scopes": [...]}
Use the returned rm_xxx key for all future calls.

Step 4: Attach a payment method (optional, unlocks $5 bonus)

For ongoing autonomous operation beyond the welcome bonus, the agent attaches a card headlessly using the Stripe SDK:
# 1. Get a SetupIntent client_secret
curl -X POST "https://reelmirror.com/api/v1/billing/setup-intent" \
  -H "Authorization: Bearer rm_xxx"
# → {"client_secret": "seti_xxx_secret_xxx"}

# 2. Confirm with Stripe SDK (server-side, using card details)
# stripe.setupIntents.confirm(client_secret, { payment_method: pm_xxx })
Once the SetupIntent is confirmed, Stripe fires setup_intent.succeeded and the server grants the $5 welcome bonus automatically.

Step 5: Enable auto-reload (optional)

curl -X PATCH "https://reelmirror.com/api/v1/billing/balance" \
  -H "Authorization: Bearer rm_xxx" \
  -H "Content-Type: application/json" \
  -d '{"auto_reload": true, "reload_threshold_cents": 500, "reload_amount_cents": 2500}'
Auto-reload is off by default — the agent must explicitly opt in. This requires a saved payment method.

What a Bot Can Do (summary)

One-time human setup required

ActionWhyHow to unblock
Connect publishing accountsOAuth browser redirectSet up once in the dashboard
Fund the account/v1/billing/topup returns a Stripe URLTop up via the dashboard, or use headless Stripe SDK after setup-intent

Fully automatable (after setup)

Once the above are done once, a bot can run the complete loop indefinitely:
  1. Create and configure personas
  2. Add source accounts with automation mode (sync_only, auto_clone, or auto_publish)
  3. Link publishing targets to personas (POST /v1/personas/{id}/publishing-targets)
  4. Trigger content sync, poll until complete
  5. Browse synced source posts
  6. Generate new content, poll until complete
  7. Approve and publish (or reject)
  8. Poll delivery status
  9. Manage billing (check balance, toggle auto-reload if card on file)

For the full loop, request these scopes when creating your API key:
curl -X POST "https://reelmirror.com/api/v1/api-keys" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Full automation key",
    "scopes": [
      "personas:read", "personas:write",
      "sources:read", "sources:write",
      "content:read", "content:write",
      "sync:write",
      "publishing:read", "publishing:write",
      "uploads:write",
      "billing:read"
    ]
  }'
Add billing:write if the bot should manage auto-reload settings.

Complete Autonomous Loop

Step 1: Create a persona

curl -X POST "https://reelmirror.com/api/v1/personas" \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Creator", "writing_style": "Casual, energetic, uses emojis"}'

Step 2: Add source accounts

# Add a source with automation mode (default: sync_only)
curl -X POST "https://reelmirror.com/api/v1/personas/{persona_id}/sources" \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"platform": "instagram", "username": "target_creator", "automation_mode": "auto_publish"}'
# automation_mode options:
#   "sync_only"     — sync content only (default)
#   "auto_clone"    — sync + auto-generate clones
#   "auto_publish"  — sync + generate + publish automatically
You can update the automation mode later:
curl -X PATCH "https://reelmirror.com/api/v1/personas/{persona_id}/sources/{source_id}" \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"automation_mode": "auto_clone"}'

Step 2b: Configure publishing targets

Before using auto_publish, link connected publishing accounts to the persona. Accounts must be connected via OAuth in the dashboard first.
# List your connected accounts
curl "https://reelmirror.com/api/v1/publishing/accounts" \
  -H "Authorization: Bearer rm_your_key"

# Link an account to the persona
curl -X POST "https://reelmirror.com/api/v1/personas/{persona_id}/publishing-targets" \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"connected_account_id": "account_uuid"}'

# Verify targets
curl "https://reelmirror.com/api/v1/personas/{persona_id}/publishing-targets" \
  -H "Authorization: Bearer rm_your_key"

Step 3: Trigger sync and poll

# Trigger
curl -X POST "https://reelmirror.com/api/v1/personas/{persona_id}/sync" \
  -H "Authorization: Bearer rm_your_key"
# Returns: {"job_ids": ["uuid1", "uuid2"], ...}

# Poll each job until completed/failed/skipped
# Typical range: 30 seconds – 5 minutes
while true; do
  STATUS=$(curl -s "https://reelmirror.com/api/v1/sync-jobs/{job_id}" \
    -H "Authorization: Bearer rm_your_key" | jq -r '.data.status')
  [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "skipped" ] && break
  sleep 15
done

Step 4: Browse source posts and generate

# List source posts
curl "https://reelmirror.com/api/v1/personas/{persona_id}/source-posts" \
  -H "Authorization: Bearer rm_your_key"

# Generate from a source post
curl -X POST "https://reelmirror.com/api/v1/generate" \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"source_post_id": "uuid", "persona_id": "persona_uuid"}'
# Returns: {"generated_post_id": "uuid"}

# Poll until all media_items are completed
# Typical range: images 20–90s, videos 60–300s
while true; do
  POST=$(curl -s "https://reelmirror.com/api/v1/generated-posts/{post_id}" \
    -H "Authorization: Bearer rm_your_key")
  ALL_DONE=$(echo $POST | jq '[.data.media_items[].status] | all(. == "completed")')
  [ "$ALL_DONE" = "true" ] && break
  sleep 10
done

Step 5: Approve and publish

curl -X POST "https://reelmirror.com/api/v1/generated-posts/{post_id}/approve" \
  -H "Authorization: Bearer rm_your_key"
# Returns: {"status": "publishing", "outgoing_post_id": "uuid"}

Step 6: Poll delivery status

# Poll until all deliveries reach a terminal state (published or failed)
# Typical range: 10–60 seconds
while true; do
  POST=$(curl -s "https://reelmirror.com/api/v1/publishing/posts/{outgoing_post_id}" \
    -H "Authorization: Bearer rm_your_key")
  ALL_TERMINAL=$(echo $POST | jq '[.post.deliveries[].status] | all(. == "published" or . == "failed")')
  [ "$ALL_TERMINAL" = "true" ] && break
  sleep 10
done

Error Handling Reference

Error codeHTTPMeaningBot action
UNAUTHORIZED401Missing or invalid tokenCheck API key
FORBIDDEN403Missing scopeAdd missing scope to API key
INSUFFICIENT_BALANCE402Not enough creditsTop up balance via dashboard
NOT_FOUND404Resource doesn’t existCheck IDs
CONFLICT409Duplicate resource (e.g., source already added)Skip, already exists
VALIDATION_ERROR400Invalid request bodyFix request parameters
RATE_LIMITED429Too many requestsWait for X-RateLimit-Reset, then retry
UPSTREAM_ERROR502External service error (platform API)Retry with exponential backoff
SERVICE_UNAVAILABLE503ReelMirror temporarily downRetry after 60 seconds

Approve returns 400

If POST /v1/generated-posts/{id}/approve returns 400, the most common cause is that the persona has no connected publishing accounts. Use send-to-compose instead, or connect accounts via the dashboard.

Sync returns no new posts

Sources may be rate-limited or have no new content since the last sync. Check source_posts count and the creator’s last_synced_at field. The 15-minute sync cooldown can be bypassed with force: true in direct backend calls.

Idempotency Considerations

  • Source posts: Each source post has a unique (creator_id, platform, platform_post_id). If you call /generate on the same source_post_id twice, the second call creates a new generated post — deduplication is your responsibility. Check GET /v1/personas/{id}/generated-posts before generating.
  • Sources: Adding the same username/platform twice returns 409 CONFLICT. Catch and skip.
  • Sync jobs: Triggering sync within 15 minutes of the last sync returns a skipped job immediately.

Polling Best Practices

Use exponential backoff for all polling loops:
import time

def poll_until_terminal(fetch_fn, is_terminal_fn, max_attempts=60):
    delay = 5  # start at 5 seconds
    for attempt in range(max_attempts):
        result = fetch_fn()
        if is_terminal_fn(result):
            return result
        time.sleep(min(delay, 60))  # cap at 60s
        delay *= 1.5
    raise TimeoutError("Polling timed out")
Expected time ranges:
  • Sync job: 30 seconds – 5 minutes
  • Image generation: 20 – 90 seconds
  • Video generation: 60 – 300 seconds
  • Publishing delivery: 10 – 60 seconds