API Documentation
Complete reference for integrating AI-powered audio transcription and content repurposing into your applications.
1.0.0Base URL https://api.repurposeapi.comAuthentication
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.Sign up at
https://repurposeapi.com/signup - 2.Navigate to Dashboard → API Keys
- 3.Click "Create New Key"
- 4.Copy and securely store your key (it won't be shown again)
Using API Keys
Include your API key in the Authorization header:
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)
JWT Tokens (Web Apps)
For browser-based applications, authenticate via Supabase Auth JWT tokens:
- 1.Sign up / Sign in via Supabase Auth (email/password, OAuth, etc.)
- 2.Get your access token from Supabase session
- 3.Include the token in all API requests
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Job-Based Async Pattern
All v1 endpoints follow an async job pattern:
- 1.Submit request → Receive
job_idimmediately - 2.Poll for status →
GET /v1/jobs/:idreturns current status - 3.Retrieve results → When
status: "completed",output_datacontains results
This pattern is ideal for long-running audio/video processing tasks.
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
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."
}'{
"job_id": "550e8400-e29b-41d4-a716-446655440000"
}Step 4 — Get Results
Poll the job endpoint until completion:
curl https://api.repurposeapi.com/v1/jobs/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer YOUR_API_KEY"
{
"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.
Endpoints
Health Check
{
"status": "ok",
"timestamp": "2025-12-15T10:30:00.000Z",
"service": "api"
}Upload Files
{
"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.Request presigned URL from this endpoint
- 2.Upload file directly to
upload_urlvia PUT request - 3.Use
download_urlin subsequent job creation requests
Complete Upload Flow
# 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
{
"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"
}| Field | Type | Description |
|---|---|---|
jobs_created | number | Jobs created this billing period |
jobs_completed | number | Successfully completed jobs |
minutes_used | number | Processing minutes consumed this month |
minutes_remaining | number | "unlimited" | Minutes remaining (30 max free) or "unlimited" for paid |
subscription_tier | string | Current plan: free, payg, builder, scale, or volume |
pending_plan | string | null | Scheduled downgrade at end of billing period |
current_period_end | string | null | ISO timestamp of billing period end |
List All Jobs
{
"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
{
"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
{
"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!"
}key value is only returned once at creation time. Store it securely — it cannot be retrieved again.Revoke API Key
{
"status": "revoked",
"message": "API key has been successfully revoked"
}Analytics
403 response.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
{
"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"
}
}
}| Field | Type | Description |
|---|---|---|
status | string | pending, processing, completed, failed |
output_data | object | null | Results when status is completed |
usage | object | null | Per-call billing summary — null for failed/pending jobs (see below) |
usage_reason | string (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.
| Field | Type | Description |
|---|---|---|
endpoint | string | The endpoint that produced the charge |
billable_unit | string | per_call, audio_minute, or transcript_minute |
quantity | number | Units billed — 1 for per-call, minutes otherwise |
rate_cents | number | Rate per unit in cents |
charge_cents | number | Total charge in cents |
charge_usd | string | Same charge formatted for display ("1.95") |
credit_balance_cents | number | Remaining credit balance after this charge |
credit_balance_usd | string | Same balance formatted for display |
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
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.
{
"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"
}
}| Status | Meaning |
|---|---|
| 200 | Job is completed and has a transcript |
| 404 | Job not found, or belongs to another user |
| 409 | Job exists but is not yet completed or has no transcript |
Transcribe Audio
job_id. Poll GET /v1/jobs/:id to check status and retrieve results.{
"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
{
"chapters": [
{ "title": "Introduction", "description": "Overview of the episode topics" },
{ "title": "Main Discussion", "description": "Deep dive into the core subject" }
]
}Generate Summaries
{
"short": "One to two sentence summary...",
"medium": "One paragraph summary...",
"long": "Two to three paragraph detailed summary..."
}Clean Text
{
"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
{
"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)
Timestamps Only (render=false)
{
"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)
{
"segments": [...],
"output_url": "https://pub-xxx.r2.dev/processed/jumpcut-123.mp3",
"original_duration": 120.0,
"edited_duration": 114.4,
"time_saved": 5.6
}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.
| Field | When to use it | Behavior |
|---|---|---|
| file_url | You 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_id | You 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_text | You already have transcript text from outside the API. | Skips Whisper entirely. Charged at the reduced transcript-minute rate. |
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
{
"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
{
"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
}
}POST /v1/clip-detect directly — it's faster since it skips transcription.Tweet Thread
{
"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
{
"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
{
"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 }
}Reference
Job Types
| Job Type | Description | Typical Duration |
|---|---|---|
transcribe | Audio/video → text transcription | 5-15 min (1hr audio) |
chapters | Generate chapter breakdowns from transcript | 1-2 min |
summarize | Generate short, medium, long summaries | 1-2 min |
social_captions | Generate platform-specific social media content | 1-2 min |
clean_text | Remove hallucinations and artifacts from transcript | 1-2 min |
clip_detect | Auto-detect best 15-30s clips for social sharing | 2-3 min |
jumpcut | Detect/remove silences from audio | 3-10 min |
podcast_to_blog | Full podcast processing pipeline | 10-20 min (1hr audio) |
tweet_thread | Media → Twitter/X thread | 2-5 min |
meeting_notes | Meeting recording → action items + summary | 3-6 min |
hook_lines | Media → scroll-stopping hook lines | 2-4 min |
newsletter_draft | Media → newsletter draft with sections | 3-7 min |
clip_finder | Full clip pipeline (transcribe + detect + social) | 5-15 min (1hr audio) |
Response Formats
Job Status Values
pending— Job created, waiting to startprocessing— Currently being processedcompleted— Successfully completedfailed— Processing failed (seeerror_message)
Polling for Results
- 1.Create job via POST endpoint
- 2.Poll
GET /v1/jobs/:idevery 2-5 seconds - 3.Check status until
completedorfailed - 4.Retrieve
output_datafrom completed job
Example Polling Logic
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": {
"code": "invalid_file_format",
"message": "Unsupported format. Use MP3, MP4, WAV, M4A, or FLAC."
}
}| Status | Meaning | Common Cause |
|---|---|---|
200 | OK | Successful request |
201 | Created | Job created successfully |
400 | Bad Request | Missing required fields or invalid format |
401 | Unauthorized | Invalid or missing API key |
402 | Payment Required | Usage quota exceeded (upgrade needed) |
404 | Not Found | Job or resource not found |
422 | Unprocessable | Validation errors |
429 | Rate Limited | Too many requests — retry with backoff |
500 | Server Error | Internal error — contact support |
Example Error Responses
{ "error": "Unauthorized" }{ "error": "filename and content_type are required" }{
"error": "Quota exceeded",
"message": "Free tier limit (30 minutes/month) reached. You've used 30.2 minutes.",
"upgrade_url": "https://repurposeapi.com/pricing"
}{
"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
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 57 X-RateLimit-Reset: 1639584000
Handling Rate Limits
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
if (response.status === 402) {
const data = await response.json()
console.log('Quota exceeded:', data.message)
console.log('Upgrade at:', data.upgrade_url)
}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.
| Plan | Processing | File Size | Rate Limit | Max Clips | Clip Detection |
|---|---|---|---|---|---|
| Free | 30 min/month (hard cap) | 50MB | 60 req/min | 2 | Basic |
| Starter — $29/mo | 500 min + $0.08/min overage | 1GB | 60 req/min | 5 | Multi-pass AI |
| Scale — $99/mo | 2,000 min + $0.05/min overage | 2GB | 60 req/min | 10 | Multi-pass AI |
Webhooks (Coming Soon)
Instead of polling for job completion, subscribe to webhook events for real-time notifications.
Setup
- 1.Go to Dashboard → Webhooks
- 2.Click "Add Endpoint"
- 3.Enter your webhook URL
- 4.Select events:
job.completed,job.failed - 5.Save and receive a signing secret
Webhook Payload
{
"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
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
| Event | Description |
|---|---|
job.completed | Job finished successfully |
job.failed | Job failed with error |
job.started | Job processing started |
SDKs & Libraries
Official SDKs (Coming Soon)
- JavaScript/TypeScript — npm package
- Python — PyPI package
REST Client Examples
# 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 (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)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)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:
// 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.
Generate Social Captions
{ "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"] } } }