Skip to content
Reference

API Documentation

Complete reference for integrating AI-powered audio transcription and content repurposing into your applications.

Version 1.0.0Base URL https://api.repurposeapi.com
01

Authentication

All API requests (except /health) require authentication. Authenticate using either API Keys (recommended for developers) or JWT tokens (for web apps).

API Keys (Recommended)

For programmatic access and integrations, use API keys.

Getting Your API Key

  1. 1.Sign up at https://repurposeapi.com/signup
  2. 2.Navigate to Dashboard → API Keys
  3. 3.Click "Create New Key"
  4. 4.Copy and securely store your key (it won't be shown again)

Using API Keys

Include your API key in the Authorization header:

bash
curl -X POST https://api.repurposeapi.com/v1/transcribe \
  -H "Authorization: Bearer rp_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"file_url": "https://..."}'

Key Formats

  • rp_test_* — Test mode (no billing, mock responses for development)
  • rp_live_* — Production mode (real processing, counts toward quota)
Important
Keep your API keys secure. Never commit them to version control or expose them in client-side code.

JWT Tokens (Web Apps)

For browser-based applications, authenticate via Supabase Auth JWT tokens:

  1. 1.Sign up / Sign in via Supabase Auth (email/password, OAuth, etc.)
  2. 2.Get your access token from Supabase session
  3. 3.Include the token in all API requests
http header
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Job-Based Async Pattern

All v1 endpoints follow an async job pattern:

  1. 1.Submit request → Receive job_id immediately
  2. 2.Poll for statusGET /v1/jobs/:id returns current status
  3. 3.Retrieve results → When status: "completed", output_data contains results

This pattern is ideal for long-running audio/video processing tasks.

02

Quickstart

Get your first API response in under 5 minutes.

Step 1 — Sign Up

Create a free account at https://repurposeapi.com/signup. No credit card required. You get 30 free minutes per month.

Step 2 — Get Your API Key

Navigate to Dashboard → API Keys and create a new key.

Step 3 — First Request

bash
curl https://api.repurposeapi.com/v1/summaries \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transcript_text": "In this episode, we discuss the future of artificial intelligence and machine learning. We explore how AI is revolutionizing healthcare, finance, and transportation. Our guest emphasizes the importance of ethical AI development and transparency."
  }'
response · json
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000"
}

Step 4 — Get Results

Poll the job endpoint until completion:

bash
curl https://api.repurposeapi.com/v1/jobs/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_API_KEY"
response · json
{
  "job": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "output_data": {
      "short": "This episode explores AI's impact on healthcare, finance, and transportation, emphasizing ethical development.",
      "medium": "A comprehensive discussion on artificial intelligence and machine learning...",
      "long": "An in-depth exploration of how artificial intelligence is transforming..."
    }
  }
}

That's it! You've made your first API call. Explore the Core Endpoints to see what else you can build.

03

Endpoints

Health Check

GET/health
Check if the API is running. No authentication required.
response · json
{
  "status": "ok",
  "timestamp": "2025-12-15T10:30:00.000Z",
  "service": "api"
}

Upload Files

POST/v1/uploads/presigned
Generate a presigned URL for uploading files to cloud storage.
filenamerequiredstringOriginal filename
content_typerequiredstringMIME type (e.g., audio/mpeg)
file_sizerequirednumberFile size in bytes (max 52,428,800 = 50MB)
response · json
{
  "upload_url": "https://...presigned-put-url...",
  "download_url": "https://pub-xxx.r2.dev/uploads/user-id/1234567890-abc123.mp3",
  "file_key": "uploads/user-id/1234567890-abc123.mp3"
}

Usage Flow

  1. 1.Request presigned URL from this endpoint
  2. 2.Upload file directly to upload_url via PUT request
  3. 3.Use download_url in subsequent job creation requests

Complete Upload Flow

bash
# Step 1: Get presigned URL
curl -X POST https://api.repurposeapi.com/v1/uploads/presigned \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "podcast.mp3",
    "content_type": "audio/mpeg",
    "file_size": 12345678
  }'

# Step 2: Upload file to presigned URL
curl -X PUT "https://...presigned-url..." \
  -H "Content-Type: audio/mpeg" \
  --data-binary "@podcast.mp3"

# Step 3: Use download_url in job creation (see Core Endpoints)

Get Usage Information

GET/v1/usage
Get current usage statistics and quota information for the authenticated user.
response · json
{
  "jobs_created": 5,
  "jobs_completed": 4,
  "minutes_used": 18.5,
  "minutes_remaining": 11.5,
  "subscription_tier": "free",
  "pending_plan": null,
  "current_period_end": "2026-02-28T23:59:59Z"
}
FieldTypeDescription
jobs_creatednumberJobs created this billing period
jobs_completednumberSuccessfully completed jobs
minutes_usednumberProcessing minutes consumed this month
minutes_remainingnumber | "unlimited"Minutes remaining (30 max free) or "unlimited" for paid
subscription_tierstringCurrent plan: free, payg, builder, scale, or volume
pending_planstring | nullScheduled downgrade at end of billing period
current_period_endstring | nullISO timestamp of billing period end

List All Jobs

GET/v1/jobs
Get all jobs for the authenticated user.
response · json
{
  "jobs": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "job_type": "transcribe",
      "created_at": "2025-12-15T10:00:00.000Z"
    }
  ]
}

API Key Management

These endpoints are for programmatic key rotation and revocation. Your first API key must be created from the Dashboard (Dashboard → API Keys), since these endpoints require existing authentication.

List API Keys

GET/v1/api-keys
List all API keys for the authenticated user.
response · json
{
  "api_keys": [
    {
      "id": "a1b2c3d4-...",
      "name": "Production Key",
      "key_prefix": "rp_live_xxxx",
      "environment": "live",
      "is_active": true,
      "last_used_at": "2026-02-20T14:00:00.000Z",
      "inserted_at": "2026-01-10T09:00:00.000Z"
    }
  ]
}

Create API Key

POST/v1/api-keys
Create a new API key.
namestringLabel for the key (default: "Unnamed Key")
environmentstring"live" or "test" (default: "live")
response · json
{
  "api_key": {
    "id": "a1b2c3d4-...",
    "name": "CI Deploy Key",
    "key_prefix": "rp_live_xxxx",
    "environment": "live",
    "is_active": true,
    "inserted_at": "2026-02-24T10:00:00.000Z"
  },
  "key": "rp_live_xxxxxxxxxxxxxxxxxxxxx",
  "warning": "Save this key now - it won't be shown again!"
}
Important
The plaintext key value is only returned once at creation time. Store it securely — it cannot be retrieved again.

Revoke API Key

DELETE/v1/api-keys/:id
Permanently revoke an API key.
response · json
{
  "status": "revoked",
  "message": "API key has been successfully revoked"
}

Analytics

GET/v1/analytics
Returns usage and processing analytics for the authenticated user. Builder or Scale plan required.
Plan Restriction
This endpoint requires a Builder or Scale subscription. Free-tier users will receive a 403 response.
04

Core Endpoints

All /v1 endpoints follow an async job pattern. Submit a request → receive a job_id → poll GET /v1/jobs/:id → retrieve results when status is "completed".

Get Job Status

GET/v1/jobs/:id
Retrieve a specific job's details and results.
response · json
{
  "job": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "job_type": "podcast_to_blog",
    "input_url": "https://pub-xxx.r2.dev/uploads/...",
    "output_data": { "...": "workflow result" },
    "error_message": null,
    "metadata": { "tone": "professional" },
    "created_at": "2025-12-15T10:00:00.000Z",
    "updated_at": "2025-12-15T10:00:30.000Z",
    "usage": {
      "endpoint": "/v1/workflows/podcast-to-blog",
      "billable_unit": "audio_minute",
      "quantity": 32.5,
      "rate_cents": 6.0,
      "charge_cents": 195.0,
      "charge_usd": "1.95",
      "credit_balance_cents": 305.0,
      "credit_balance_usd": "3.05"
    }
  }
}
FieldTypeDescription
statusstringpending, processing, completed, failed
output_dataobject | nullResults when status is completed
usageobject | nullPer-call billing summary — null for failed/pending jobs (see below)
usage_reasonstring (optional)Reason usage is null when determinate — currently only "job_failed_not_billed"

The `usage` block

Every job response carries a usage field describing how that call was priced. Designed so callers can budget against the API without separate billing lookups.

FieldTypeDescription
endpointstringThe endpoint that produced the charge
billable_unitstringper_call, audio_minute, or transcript_minute
quantitynumberUnits billed — 1 for per-call, minutes otherwise
rate_centsnumberRate per unit in cents
charge_centsnumberTotal charge in cents
charge_usdstringSame charge formatted for display ("1.95")
credit_balance_centsnumberRemaining credit balance after this charge
credit_balance_usdstringSame balance formatted for display
When usage is null
Pending and processing jobs have usage: null with no reason — the charge isn't determined yet. Failed jobs have usage: null with usage_reason: "job_failed_not_billed". Workflows chained from source_job_id ortranscript_text carry billable_unit: "transcript_minute" at the reduced rate.

Get Job Transcript

GET/v1/jobs/:id/transcript
Inspect the transcript a completed job produced. Useful before chaining a workflow with source_job_id.

Returns the transcript text, segments, language, and duration from any completed job whose output_data contains a transcript (e.g. transcribe, clip-finder, and any workflow that produced one). Cross-user lookups return 404.

response · json
{
  "transcript": {
    "text": "Full transcript text...",
    "segments": [{ "start": 0.0, "end": 5.2, "text": "First segment..." }],
    "language": "en",
    "duration": 1800.5,
    "source_job_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
StatusMeaning
200Job is completed and has a transcript
404Job not found, or belongs to another user
409Job exists but is not yet completed or has no transcript

Transcribe Audio

POST/v1/transcribe
Transcribe audio/video to text using Whisper AI.
file_urlrequiredstringURL of the audio/video file (from upload endpoint)
modelstringWhisper model: tiny, base, small, medium (default: base)
Job-based Processing
All v1 endpoints return immediately with a job_id. Poll GET /v1/jobs/:id to check status and retrieve results.
output · json
{
  "transcript": "Full transcription text...",
  "segments": [
    { "start": 0.0, "end": 5.2, "text": "Hello and welcome to the podcast." }
  ],
  "language": "en",
  "duration": 3600.5
}

Generate Chapters

POST/v1/chapters
Generate chapter breakdowns from transcript text.
transcript_textrequiredstringThe full transcript text to analyze
titlestringTitle for context (default: "Untitled")
output · json
{
  "chapters": [
    { "title": "Introduction", "description": "Overview of the episode topics" },
    { "title": "Main Discussion", "description": "Deep dive into the core subject" }
  ]
}

Generate Summaries

POST/v1/summaries
Generate short, medium, and long summaries from transcript text.
transcript_textrequiredstringThe full transcript text to summarize
output · json
{
  "short": "One to two sentence summary...",
  "medium": "One paragraph summary...",
  "long": "Two to three paragraph detailed summary..."
}

Generate Social Captions

POST/v1/social-captions
Generate platform-specific social media captions from transcript text.
transcript_textrequiredstringThe full transcript text
titlestringTitle for context (default: "Untitled")
platformsarrayPlatforms: twitter, linkedin, instagram, tiktok (default: all)
output · json
{
  "captions": {
    "twitter": {
      "text": "Just learned the #1 mistake founders make when scaling...",
      "hashtags": ["#startup", "#founders", "#scaleup"],
      "char_count": 142
    },
    "linkedin": {
      "text": "In today's episode, we explore the critical mistakes...",
      "hashtags": ["#leadership", "#business", "#entrepreneurship"],
      "word_count": 180
    },
    "instagram": {
      "caption": "🚀 What if I told you most founders fail at scaling for ONE reason?",
      "hashtags": ["#podcast", "#business", "#motivation", "#startup"]
    },
    "tiktok": {
      "caption": "Wait for the plot twist at 0:45 👀",
      "hashtags": ["#podcast", "#viral", "#fyp", "#business"]
    }
  }
}

Clean Text

POST/v1/clean-text
Remove hallucinations and artifacts from AI-generated transcripts.
transcript_textrequiredstringThe transcript text to clean
fix_timestampsbooleanRemove hallucinated timestamps (default: true)
remove_repetitionsbooleanCollapse repeated phrases (default: true)
remove_music_artifactsbooleanRemove [Music], [Applause], etc. (default: true)
remove_filler_wordsbooleanRemove um, uh, like, you know (default: true)
output · json
{
  "cleaned_text": "In this episode, we discuss the three pillars of...",
  "changes": {
    "timestamps_removed": 5,
    "repetitions_fixed": 12,
    "music_artifacts_removed": 3,
    "filler_words_removed": 47
  },
  "original_length": 1520,
  "cleaned_length": 1345
}

Detect Best Clips

POST/v1/clip-detect
Auto-detect the best 15-30 second clips for social sharing.
transcript_textrequiredstringThe full transcript text
segmentsarrayTranscript segments with timestamps (if available)
target_durationnumberTarget clip duration in seconds (default: 20)
num_clipsnumberNumber of clips to return (default: 5)
output · json
{
  "clips": [
    {
      "rank": 1,
      "start": 145.2,
      "end": 165.8,
      "duration": 20.6,
      "text": "This is the most important insight...",
      "score": 0.92,
      "reason": "High engagement moment discussing key insight with emotional impact"
    }
  ]
}

Remove Silences (Jumpcut)

POST/v1/jumpcut
Detect and optionally remove silences from audio files.
file_urlrequiredstringURL of the audio file (from upload endpoint)
silence_threshold_dbnumberSilence detection threshold in dB (default: -40)
min_silence_duration_msnumberMinimum silence duration to remove in ms (default: 500)
renderbooleanGenerate edited audio file (default: false)

Timestamps Only (render=false)

output · json
{
  "segments": [
    {"start": 0.0, "end": 12.5},
    {"start": 15.2, "end": 45.8},
    {"start": 48.1, "end": 120.0}
  ],
  "original_duration": 120.0,
  "edited_duration": 114.4,
  "time_saved": 5.6
}

With Rendered File (render=true)

output · json
{
  "segments": [...],
  "output_url": "https://pub-xxx.r2.dev/processed/jumpcut-123.mp3",
  "original_duration": 120.0,
  "edited_duration": 114.4,
  "time_saved": 5.6
}
05

Workflows

Workflow endpoints combine multiple operations for convenience. Each workflow transcribes the audio first, then runs the specified processing pipeline.

Input Modes

Every workflow except clip-finder accepts the transcript source in one of three forms. Provide exactly one of file_url, source_job_id, or transcript_text.

FieldWhen to use itBehavior
file_urlYou have an audio or video file to process from scratch.The worker downloads the file and runs Whisper transcription before the workflow LLM step.
source_job_idYou already ran another job that produced a transcript (e.g. clip-finder, transcribe).Reuses that transcript — no re-transcription, no audio download. Charged at the reduced transcript-minute rate.
transcript_textYou already have transcript text from outside the API.Skips Whisper entirely. Charged at the reduced transcript-minute rate.
Cross-user safety
source_job_id is scoped to the calling user. Looking up another user's job returns 404, not 403 — callers can't distinguish "doesn't exist" from "exists but belongs to someone else."

Podcast to Blog

POST/v1/workflows/podcast-to-blog
Transform podcast audio into a complete blog post with transcript, summary, chapters, and blog draft.
file_urlstringURL of the audio/video file (from upload endpoint). One of file_url, source_job_id, or transcript_text.
source_job_idstringUUID of a completed job whose transcript will be reused (no re-transcription).
transcript_textstringPre-existing transcript text (skips Whisper).
titlestringPodcast episode title (default: "Untitled")
tonestringBlog tone: professional, casual, technical, conversational (default: professional)
output · json
{
  "transcript": "Full transcription...",
  "summary": "This podcast discusses strategies for scaling...",
  "chapters": [
    { "title": "Introduction", "start_time": "00:00", "summary": "Overview of scaling challenges" },
    { "title": "Hiring Strategy", "start_time": "05:30", "summary": "How to build the right team" }
  ],
  "blog_draft": "# How to Scale Your Startup\n\n## Introduction\n\n...",
  "language": "en",
  "duration": 3600.5
}

Clip Finder

POST/v1/workflows/clip-finder
Upload a media file and get back ranked, shareable clips — optionally enriched with platform-ready social captions.
file_urlrequiredstringURL of the audio/video file (from upload endpoint)
num_clipsintegerNumber of clips to return (default: 5)
target_durationintegerTarget clip length in seconds (default: 20)
enrichbooleanInclude social captions per clip (default: false)
platformsarrayPlatforms for enrichment: twitter, linkedin, instagram, tiktok (default: all)
Tier-specific Clip Limits
The number of clips returned is capped by your plan. Starter and Scale plans include Advanced AI Ranking, a multi-pass semantic analysis that produces more differentiated clip scores.
output · json
{
  "transcript": "Full transcription...",
  "segments": [{ "start": 0.0, "end": 5.2, "text": "First segment..." }],
  "duration": 1800.5,
  "language": "en",
  "clips": [
    {
      "rank": 1, "start": 30.0, "end": 52.0, "duration": 22.0,
      "text": "The clip transcript text...",
      "score": 0.92,
      "reason": "Strong hook with a quotable takeaway",
      "social": {
        "hook": "This changes everything about AI",
        "twitter": { "text": "...", "hashtags": ["AI", "Podcasting"] },
        "linkedin": { "text": "...", "hashtags": ["AIInsights"] },
        "instagram": { "caption": "...", "hashtags": ["AI", "PodcastClips"] },
        "tiktok": { "caption": "...", "hashtags": ["ai", "fyp"] }
      }
    }
  ],
  "processing_time": {
    "transcription": 45.2, "clip_detection": 8.1, "enrichment": 5.3, "total": 58.6
  }
}
Tip
If you already have a transcript, use POST /v1/clip-detect directly — it's faster since it skips transcription.

Tweet Thread

POST/v1/workflows/tweet-thread
Upload a media file and get back a ready-to-post Twitter/X thread.
file_urlstringURL of the audio/video file (from upload endpoint). One of file_url, source_job_id, or transcript_text.
source_job_idstringUUID of a completed job whose transcript will be reused (no re-transcription).
transcript_textstringPre-existing transcript text (skips Whisper).
thread_lengthstringshort (3-5 tweets), medium (6-10), long (10-15). Default: medium
tonestringengaging, professional, casual, witty. Default: engaging
output · json
{
  "transcript": "Full transcript...",
  "thread": [
    { "tweet_number": 1, "text": "1/ Hook tweet...", "char_count": 142 }
  ],
  "thread_count": 7,
  "topic": "One-line topic summary",
  "processing_time": { "transcription": 12.5, "thread_generation": 3.2, "total": 15.7 }
}

Meeting Notes

POST/v1/workflows/meeting-notes
Upload a meeting recording and get structured action items, decisions, and summary.
file_urlstringURL of the audio/video file (from upload endpoint). One of file_url, source_job_id, or transcript_text.
source_job_idstringUUID of a completed job whose transcript will be reused (no re-transcription).
transcript_textstringPre-existing transcript text (skips Whisper).
meeting_titlestringTitle for context (default: "Untitled Meeting")
output · json
{
  "transcript": "Full transcript...",
  "summary": "Meeting summary...",
  "action_items": [
    { "id": 1, "action": "Finalize spec", "owner": "Sarah", "priority": "high", "deadline_suggestion": "by Friday" }
  ],
  "key_decisions": ["Launch date set for March 15"],
  "main_topics": ["Roadmap", "Testing"],
  "processing_time": { "transcription": 18.3, "extraction": 4.1, "total": 22.4 }
}

Hook Lines

POST/v1/workflows/hook-lines
Extract compelling, scroll-stopping hook lines from any audio or video.
file_urlstringURL of the audio/video file (from upload endpoint). One of file_url, source_job_id, or transcript_text.
source_job_idstringUUID of a completed job whose transcript will be reused (no re-transcription).
transcript_textstringPre-existing transcript text (skips Whisper).
num_hooksintegerNumber of hooks to extract, 5-10 (default: 8)
stylestringviral, professional, storytelling (default: viral)
output · json
{
  "transcript": "Full transcript...",
  "hooks": [
    {
      "text": "Most people are productive for only 3 hours a day.",
      "type": "statistic",
      "source_timestamp": 45.0,
      "why_it_works": "Specific number creates curiosity"
    }
  ],
  "processing_time": { "transcription": 10.2, "hook_extraction": 3.8, "total": 14.0 }
}

Newsletter Draft

POST/v1/workflows/newsletter-draft
Turn a podcast or video into a ready-to-send newsletter with subject line, sections, and CTA.
file_urlstringURL of the audio/video file (from upload endpoint). One of file_url, source_job_id, or transcript_text.
source_job_idstringUUID of a completed job whose transcript will be reused (no re-transcription).
transcript_textstringPre-existing transcript text (skips Whisper).
newsletter_namestringNewsletter name for branding
tonestringprofessional, casual, friendly, authoritative (default: professional)
output · json
{
  "transcript": "Full transcript...",
  "newsletter": {
    "subject_line": "3 lessons from building in public",
    "preview_text": "What a year of sharing everything taught me",
    "sections": [
      { "heading": "The Uncomfortable Truth", "body": "Section content..." }
    ],
    "call_to_action": "Hit reply and tell me..."
  },
  "summary": { "short": "...", "medium": "...", "long": "..." },
  "processing_time": { "transcription": 15.0, "summary": 3.5, "newsletter_generation": 5.2, "total": 23.7 }
}
06

Reference

Job Types

Job TypeDescriptionTypical Duration
transcribeAudio/video → text transcription5-15 min (1hr audio)
chaptersGenerate chapter breakdowns from transcript1-2 min
summarizeGenerate short, medium, long summaries1-2 min
social_captionsGenerate platform-specific social media content1-2 min
clean_textRemove hallucinations and artifacts from transcript1-2 min
clip_detectAuto-detect best 15-30s clips for social sharing2-3 min
jumpcutDetect/remove silences from audio3-10 min
podcast_to_blogFull podcast processing pipeline10-20 min (1hr audio)
tweet_threadMedia → Twitter/X thread2-5 min
meeting_notesMeeting recording → action items + summary3-6 min
hook_linesMedia → scroll-stopping hook lines2-4 min
newsletter_draftMedia → newsletter draft with sections3-7 min
clip_finderFull clip pipeline (transcribe + detect + social)5-15 min (1hr audio)

Response Formats

Job Status Values

  • pending — Job created, waiting to start
  • processing — Currently being processed
  • completed — Successfully completed
  • failed — Processing failed (see error_message)

Polling for Results

  1. 1.Create job via POST endpoint
  2. 2.Poll GET /v1/jobs/:id every 2-5 seconds
  3. 3.Check status until completed or failed
  4. 4.Retrieve output_data from completed job

Example Polling Logic

javascript
async function waitForJobCompletion(jobId, token) {
  while (true) {
    const response = await fetch(
      `https://api.repurposeapi.com/v1/jobs/${jobId}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    )
    const { job } = await response.json()

    if (job.status === 'completed') return job.output_data
    if (job.status === 'failed') throw new Error(job.error_message)

    // Wait 3 seconds before polling again
    await new Promise(resolve => setTimeout(resolve, 3000))
  }
}

Error Handling

Standard HTTP status codes. All errors return consistent JSON:

error response · json
{
  "error": {
    "code": "invalid_file_format",
    "message": "Unsupported format. Use MP3, MP4, WAV, M4A, or FLAC."
  }
}
StatusMeaningCommon Cause
200OKSuccessful request
201CreatedJob created successfully
400Bad RequestMissing required fields or invalid format
401UnauthorizedInvalid or missing API key
402Payment RequiredUsage quota exceeded (upgrade needed)
404Not FoundJob or resource not found
422UnprocessableValidation errors
429Rate LimitedToo many requests — retry with backoff
500Server ErrorInternal error — contact support

Example Error Responses

401 · json
{ "error": "Unauthorized" }
400 · json
{ "error": "filename and content_type are required" }
402 · json
{
  "error": "Quota exceeded",
  "message": "Free tier limit (30 minutes/month) reached. You've used 30.2 minutes.",
  "upgrade_url": "https://repurposeapi.com/pricing"
}
429 · json
{
  "error": "Rate limit exceeded",
  "message": "Maximum 60 requests per minute allowed",
  "retry_after": 60
}

Rate Limits & Quotas

Rate Limiting

  • 60 requests per minute per authenticated user
  • Applies to all authenticated endpoints
  • Rolling 60-second window

Response Headers

http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1639584000

Handling Rate Limits

javascript
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After') || 60
  await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
  // Retry request
}

Handling Quota Limits

javascript
if (response.status === 402) {
  const data = await response.json()
  console.log('Quota exceeded:', data.message)
  console.log('Upgrade at:', data.upgrade_url)
}
Tip
Check your usage regularly with GET /v1/usage to avoid hitting limits mid-workflow.

What Counts as a Request

  • Each job creation counts as 1 request
  • Polling job status does not count
  • Webhook deliveries do not count
  • Failed jobs do not count toward quota

Supported Formats

Audio: MP3, WAV, M4A, AAC, OGG, FLAC, WMA, AIFF

Video: MP4, MOV, AVI, MKV, WEBM, FLV, M4V

Files are automatically converted to the optimal format for processing. Video files have their audio track extracted before transcription.

Pricing Plans

For current pricing tiers and details, see the Pricing Page.

PlanProcessingFile SizeRate LimitMax ClipsClip Detection
Free30 min/month (hard cap)50MB60 req/min2Basic
Starter — $29/mo500 min + $0.08/min overage1GB60 req/min5Multi-pass AI
Scale — $99/mo2,000 min + $0.05/min overage2GB60 req/min10Multi-pass AI

Webhooks (Coming Soon)

Instead of polling for job completion, subscribe to webhook events for real-time notifications.

Setup

  1. 1.Go to Dashboard → Webhooks
  2. 2.Click "Add Endpoint"
  3. 3.Enter your webhook URL
  4. 4.Select events: job.completed, job.failed
  5. 5.Save and receive a signing secret

Webhook Payload

json
{
  "event": "job.completed",
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "job_type": "transcribe",
  "user_id": "user_xxxxx",
  "timestamp": "2025-12-23T10:00:00Z",
  "data": { "output_data": { ... } }
}

Verifying Webhooks

javascript
const crypto = require('crypto')

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )
}

Event Types

EventDescription
job.completedJob finished successfully
job.failedJob failed with error
job.startedJob processing started
Note
Webhook functionality is currently in development. Join the waitlist to be notified when available.

SDKs & Libraries

Official SDKs (Coming Soon)

  • JavaScript/TypeScript — npm package
  • Python — PyPI package

REST Client Examples

bash
# cURL — Create transcription job
curl -X POST https://api.repurposeapi.com/v1/transcribe \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "file_url": "https://pub-xxx.r2.dev/uploads/file.mp3",
    "model": "base"
  }'
javascript
// JavaScript (Fetch)
const response = await fetch('https://api.repurposeapi.com/v1/workflows/podcast-to-blog', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    file_url: 'https://pub-xxx.r2.dev/uploads/file.mp3',
    title: 'My Podcast Episode',
    tone: 'professional'
  })
})

const { job } = await response.json()
console.log('Job created:', job.id)
python
import requests
import time

# Create a transcription job
response = requests.post(
    'https://api.repurposeapi.com/v1/transcribe',
    headers={'Authorization': f'Bearer {api_key}'},
    json={
        'file_url': 'https://pub-xxx.r2.dev/uploads/file.mp3',
        'model': 'base'
    }
)

job_id = response.json()['job_id']
print(f"Job created: {job_id}")

# Poll for completion
while True:
    job_response = requests.get(
        f'https://api.repurposeapi.com/v1/jobs/{job_id}',
        headers={'Authorization': f'Bearer {api_key}'}
    )
    job = job_response.json()['job']

    if job['status'] == 'completed':
        print("Transcript:", job['output_data']['transcript'])
        break
    elif job['status'] == 'failed':
        print("Error:", job['error_message'])
        break

    time.sleep(3)
ruby
require 'net/http'
require 'json'
require 'uri'

# Create a summaries job
uri = URI('https://api.repurposeapi.com/v1/summaries')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{api_key}"
request['Content-Type'] = 'application/json'
request.body = {
  transcript_text: 'Sample podcast transcript...'
}.to_json

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

job_id = JSON.parse(response.body)['job_id']
puts "Job created: #{job_id}"

Complete Example: Podcast to Blog

Here's a complete workflow from upload to blog draft:

javascript
// 1. Get presigned upload URL
const uploadRes = await fetch('https://api.repurposeapi.com/v1/uploads/presigned', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    filename: 'podcast.mp3',
    content_type: 'audio/mpeg'
  })
})
const { upload_url, download_url } = await uploadRes.json()

// 2. Upload file to storage
await fetch(upload_url, {
  method: 'PUT',
  headers: { 'Content-Type': 'audio/mpeg' },
  body: audioFile
})

// 3. Create podcast-to-blog job
const jobRes = await fetch('https://api.repurposeapi.com/v1/workflows/podcast-to-blog', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    file_url: download_url,
    title: 'How to Scale Your Startup',
    tone: 'professional'
  })
})
const { job } = await jobRes.json()

// 4. Poll for completion
let completed = false
while (!completed) {
  await new Promise(resolve => setTimeout(resolve, 3000))

  const statusRes = await fetch(`https://api.repurposeapi.com/v1/jobs/${job.id}`, {
    headers: { 'Authorization': `Bearer ${token}` }
  })
  const { job: updatedJob } = await statusRes.json()

  if (updatedJob.status === 'completed') {
    console.log('Transcript:', updatedJob.output_data.transcript)
    console.log('Blog Draft:', updatedJob.output_data.blog_draft)
    completed = true
  } else if (updatedJob.status === 'failed') {
    throw new Error(updatedJob.error_message)
  }
}

Support & Feedback

  • Documentation: https://repurposeapi.com/docs
  • Issues: https://github.com/repurposeapi/issues
  • Email: support@repurposeapi.com
  • Discord: Coming soon

For the full release history, see the Changelog.