ELBO AI, INC Audio Uploads API
The Audio Uploads API from ELBO AI, INC — 1 operation(s) for audio uploads.
The Audio Uploads API from ELBO AI, INC — 1 operation(s) for audio uploads.
openapi: 3.1.0
info:
title: Puppetry Developer API Beta Audio Uploads API
description: Live Puppetry Developer API beta for Puppetry Voice API access, hosted audio uploads, usage, and credit-backed text/audio-to-video jobs.
version: '2026-05-11'
contact:
name: Puppetry Support
url: https://www.puppetry.com
email: support@puppetry.com
termsOfService: https://www.puppetry.com/tos
license:
name: Proprietary
servers:
- url: https://www.puppetry.com
description: Production
security:
- developerApiKey: []
tags:
- name: Audio Uploads
paths:
/api/v1/uploads/audio-url:
post:
operationId: createHostedAudioUploadUrl
x-openai-isConsequential: false
summary: Create a signed hosted audio upload URL
description: Creates a short-lived signed PUT URL for existing audio and a short-lived read URL for the uploaded asset. Send an Idempotency-Key header, or idempotencyKey/idempotency_key in the JSON body when custom headers are unavailable, so a lost response does not reserve duplicate hosted-upload quota. For audio-to-video workflows, send require_video_readiness=true to fail before reserving upload quota when video credits or active slots are blocked; active-slot blockers and retryable transient failures include Retry-After and X-Puppetry-Next-Poll-At so agents can pace the next reservation attempt. Idempotent replays of an existing pending upload still return a fresh signed URL. Requires the uploads:create scope.
tags:
- Audio Uploads
security:
- developerApiKey: []
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AudioUploadRequest'
examples:
audioUpload:
summary: Create upload URL
value:
contentType: audio/mpeg
contentLength: 1048576
idempotencyKey: upload-demo-1
requireVideoReadiness: true
responses:
'200':
description: Signed upload and read URLs
headers:
X-RateLimit-Limit:
$ref: '#/components/headers/RateLimitLimit'
X-RateLimit-Remaining:
$ref: '#/components/headers/RateLimitRemaining'
X-RateLimit-Reset:
$ref: '#/components/headers/RateLimitReset'
content:
application/json:
schema:
$ref: '#/components/schemas/AudioUploadUrlResponse'
'400':
$ref: '#/components/responses/InvalidRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'403':
$ref: '#/components/responses/Forbidden'
'409':
description: Idempotency-Key replay is in progress or conflicts with existing upload metadata.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
X-Puppetry-Next-Poll-At:
$ref: '#/components/headers/NextPollAt'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
idempotencyInProgress:
value:
error: idempotency_key_in_progress
message: An upload URL with this Idempotency-Key is already being created. Retry this request shortly.
retryable: true
retry_after_seconds: 5
retryAfter: 5
retryAfterSeconds: 5
next_poll_at: '2026-06-22T08:50:05.000Z'
nextPollAt: '2026-06-22T08:50:05.000Z'
details:
retryable: true
retry_after_seconds: 5
retryAfter: 5
retryAfterSeconds: 5
next_poll_at: '2026-06-22T08:50:05.000Z'
nextPollAt: '2026-06-22T08:50:05.000Z'
idempotencyKeyConflict:
value:
error: idempotency_key_conflict
message: Idempotency-Key was already used with different upload metadata
details:
retryable: false
retry_blocked_reason: Idempotency-Key was already used with different upload metadata
retryBlockedReason: Idempotency-Key was already used with different upload metadata
retry_block_code: AUDIO_UPLOAD_IDEMPOTENCY_KEY_CONFLICT
retryBlockCode: AUDIO_UPLOAD_IDEMPOTENCY_KEY_CONFLICT
'413':
description: Request body or audio file exceeds the upload limits
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
requestBodyTooLarge:
value:
error: payload_too_large
message: Request body must be 8192 bytes or fewer
details:
max_body_bytes: 8192
retryable: false
retry_blocked_reason: Request body must be 8192 bytes or fewer
retryBlockedReason: Request body must be 8192 bytes or fewer
retry_block_code: AUDIO_UPLOAD_PAYLOAD_TOO_LARGE
retryBlockCode: AUDIO_UPLOAD_PAYLOAD_TOO_LARGE
fileTooLarge:
value:
error: file_too_large
message: Audio uploads are limited to 50MB
details:
max_file_bytes: 52428800
retryable: false
retry_blocked_reason: Audio uploads are limited to 50MB
retryBlockedReason: Audio uploads are limited to 50MB
retry_block_code: AUDIO_UPLOAD_FILE_TOO_LARGE
retryBlockCode: AUDIO_UPLOAD_FILE_TOO_LARGE
'415':
description: Request Content-Type must be application/json
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
unsupportedMediaType:
value:
error: unsupported_media_type
message: Content-Type must be application/json
details:
retryable: false
retry_blocked_reason: Content-Type must be application/json
retryBlockedReason: Content-Type must be application/json
retry_block_code: UNSUPPORTED_AUDIO_UPLOAD_MEDIA_TYPE
retryBlockCode: UNSUPPORTED_AUDIO_UPLOAD_MEDIA_TYPE
'429':
description: Video generation active slots are full, or the Developer API rate limit was exceeded.
headers:
X-RateLimit-Limit:
$ref: '#/components/headers/RateLimitLimit'
X-RateLimit-Remaining:
$ref: '#/components/headers/RateLimitRemaining'
X-RateLimit-Reset:
$ref: '#/components/headers/RateLimitReset'
Retry-After:
$ref: '#/components/headers/RetryAfter'
X-Puppetry-Next-Poll-At:
$ref: '#/components/headers/NextPollAt'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
videoSlotsFull:
value:
error: concurrent_job_limit_reached
message: Developer API video generation is at its active job limit. Retry after the active job slot expires.
retryable: true
retry_after_seconds: 512
retryAfter: 512
retryAfterSeconds: 512
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
details:
retryable: true
code: concurrent_job_limit_reached
reason: concurrent_job_limit_reached
video_generation:
can_create: false
canCreate: false
reason: concurrent_job_limit_reached
active_jobs: 2
activeJobs: 2
available_slots: 0
availableSlots: 0
retry_after_seconds: 512
retryAfter: 512
retryAfterSeconds: 512
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
videoGeneration:
can_create: false
canCreate: false
reason: concurrent_job_limit_reached
active_jobs: 2
activeJobs: 2
available_slots: 0
availableSlots: 0
retry_after_seconds: 512
retryAfter: 512
retryAfterSeconds: 512
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
rateLimited:
value:
error: rate_limited
message: 'Developer API beta limit exceeded: 10 requests per minute.'
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
details:
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
'502':
$ref: '#/components/responses/UpstreamFailure'
'503':
$ref: '#/components/responses/TemporarilyUnavailable'
components:
responses:
Forbidden:
description: The API key does not include the required endpoint scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
forbidden:
value:
error: forbidden
message: This API key does not have the required scope
InsufficientCredits:
description: The account does not have enough video credits to create the job.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
insufficientCredits:
value:
error: insufficient_credits
message: Not enough video credits to create a video job
details:
code: insufficient_video_credits
reason: insufficient_video_credits
required_credits: 1
requiredCredits: 1
remaining_credits: 0
remainingCredits: 0
retryable: false
retry_blocked_reason: Developer API video generation needs 1 credit, but only 0 remain.
retryBlockedReason: Developer API video generation needs 1 credit, but only 0 remain.
retry_block_code: INSUFFICIENT_VIDEO_CREDITS
retryBlockCode: INSUFFICIENT_VIDEO_CREDITS
checkout_url: https://www.puppetry.com/pricing#pricing-credit-packs
checkoutUrl: https://www.puppetry.com/pricing#pricing-credit-packs
top_up: Buy 5 video credits for $4.99 at https://www.puppetry.com/pricing#pricing-credit-packs
topUp: Buy 5 video credits for $4.99 at https://www.puppetry.com/pricing#pricing-credit-packs
video_generation:
can_create: false
canCreate: false
required_credits: 1
requiredCredits: 1
credits_remaining: 0
creditsRemaining: 0
concurrent_jobs: 2
concurrentJobs: 2
active_jobs: 0
activeJobs: 0
available_slots: 2
availableSlots: 2
reason: insufficient_video_credits
videoGeneration:
can_create: false
canCreate: false
required_credits: 1
requiredCredits: 1
credits_remaining: 0
creditsRemaining: 0
concurrent_jobs: 2
concurrentJobs: 2
active_jobs: 0
activeJobs: 0
available_slots: 2
availableSlots: 2
reason: insufficient_video_credits
Unauthorized:
description: Missing, malformed, invalid, or revoked API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
unauthorized:
value:
error: unauthorized
message: 'Missing or invalid Authorization header. Use: Bearer pk_live_...'
TemporarilyUnavailable:
description: A beta quota, usage lookup, or job counter is temporarily unavailable.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
X-Puppetry-Next-Poll-At:
$ref: '#/components/headers/NextPollAt'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
apiAuthUnavailable:
value:
error: api_auth_unavailable
message: Developer API authentication is temporarily unavailable
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
details:
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
InvalidRequest:
description: Invalid request body or unsupported input
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
hostedAudioUploadUnavailable:
value:
error: hosted_audio_upload_unavailable
message: Hosted audio upload ownership is temporarily unavailable
details:
retry_after_seconds: 217
retryAfter: 217
retryAfterSeconds: 217
hostedAudioUploadNotFound:
value:
error: invalid_request
message: audio_url must reference a hosted API audio upload owned by this API key
details:
retryable: false
retry_blocked_reason: audio_url must reference a hosted API audio upload owned by this API key
retryBlockedReason: audio_url must reference a hosted API audio upload owned by this API key
retry_block_code: HOSTED_AUDIO_UPLOAD_NOT_FOUND
retryBlockCode: HOSTED_AUDIO_UPLOAD_NOT_FOUND
hostedAudioUploadPending:
value:
error: invalid_request
message: audio_url hosted upload has not finished uploading; upload the file before creating a video
details:
retryable: false
retry_blocked_reason: audio_url hosted upload has not finished uploading; upload the file before creating a video
retryBlockedReason: audio_url hosted upload has not finished uploading; upload the file before creating a video
retry_block_code: HOSTED_AUDIO_UPLOAD_PENDING
retryBlockCode: HOSTED_AUDIO_UPLOAD_PENDING
invalidImage:
value:
error: invalid_image
message: image_url must be a supported image
details:
retryable: false
retry_blocked_reason: image_url must be a supported image
retryBlockedReason: image_url must be a supported image
retry_block_code: INVALID_VIDEO_IMAGE
retryBlockCode: INVALID_VIDEO_IMAGE
audioTooLong:
value:
error: audio_too_long
message: Audio is too long (529s). Maximum supported duration is 300 seconds (5 minutes). Please use a shorter script or split into multiple videos.
details:
duration_seconds: 529.42
max_duration_seconds: 300
retryable: false
retry_blocked_reason: Audio is too long (529s). Maximum supported duration is 300 seconds (5 minutes). Please use a shorter script or split into multiple videos.
retryBlockedReason: Audio is too long (529s). Maximum supported duration is 300 seconds (5 minutes). Please use a shorter script or split into multiple videos.
retry_block_code: VIDEO_CREATE_AUDIO_TOO_LONG
retryBlockCode: VIDEO_CREATE_AUDIO_TOO_LONG
UpstreamFailure:
description: Puppetry could not create or inspect the requested asset.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
X-Puppetry-Next-Poll-At:
$ref: '#/components/headers/NextPollAt'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
videoQueueFailed:
value:
error: video_queue_failed
message: Animation service did not return a video job id
details:
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
creditDebitUnavailable:
value:
error: credit_debit_unavailable
message: Video credit debit is temporarily unavailable
details:
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
next_poll_at: '2026-06-22T08:50:30.000Z'
nextPollAt: '2026-06-22T08:50:30.000Z'
assetProbeUnavailable:
value:
error: asset_probe_unavailable
message: audio_url is temporarily unavailable for inspection
details:
retryable: true
retry_after_seconds: 30
retryAfter: 30
retryAfterSeconds: 30
jobStatusUnavailable:
value:
error: job_status_unavailable
message: Video job status is temporarily unavailable
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
details:
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
object: video_job
id: api_video_abc123
job_id: api_video_abc123
jobId: api_video_abc123
task_id: api_video_abc123
taskId: api_video_abc123
status_url: /api/v1/videos/api_video_abc123
statusUrl: /api/v1/videos/api_video_abc123
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
created_at: '2026-06-22T08:50:00.000Z'
createdAt: '2026-06-22T08:50:00.000Z'
content_location: /api/v1/videos/api_video_abc123
contentLocation: /api/v1/videos/api_video_abc123
jobResultUnavailable:
value:
error: job_result_unavailable
message: Video job result is temporarily unavailable
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
details:
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
object: video_job
id: api_video_abc123
job_id: api_video_abc123
jobId: api_video_abc123
task_id: api_video_abc123
taskId: api_video_abc123
status_url: /api/v1/videos/api_video_abc123
statusUrl: /api/v1/videos/api_video_abc123
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
created_at: '2026-06-22T08:50:00.000Z'
createdAt: '2026-06-22T08:50:00.000Z'
content_location: /api/v1/videos/api_video_abc123
contentLocation: /api/v1/videos/api_video_abc123
jobFinalizationUnavailable:
value:
error: job_finalization_unavailable
message: Video job finalization is temporarily unavailable
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
details:
retryable: true
retry_after_seconds: 10
retryAfter: 10
retryAfterSeconds: 10
next_poll_at: '2026-06-22T08:50:10.000Z'
nextPollAt: '2026-06-22T08:50:10.000Z'
object: video_job
id: api_video_abc123
job_id: api_video_abc123
jobId: api_video_abc123
task_id: api_video_abc123
taskId: api_video_abc123
status_url: /api/v1/videos/api_video_abc123
statusUrl: /api/v1/videos/api_video_abc123
credits:
charged: 0
remaining: null
creation_credits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
creationCredits:
charged: 1
remaining_after_debit: 299
remainingAfterDebit: 299
created_at: '2026-06-22T08:50:00.000Z'
createdAt: '2026-06-22T08:50:00.000Z'
content_location: /api/v1/videos/api_video_abc123
contentLocation: /api/v1/videos/api_video_abc123
headers:
NextPollAt:
description: Absolute UTC retry or poll time. Mirrors next_poll_at/nextPollAt in the JSON body.
schema:
type: string
format: date-time
example: '2026-06-22T08:50:30.000Z'
RetryAfter:
description: Seconds to wait before retrying a limited/transient request or polling a non-terminal video job again.
schema:
type: integer
example: 30
RateLimitRemaining:
description: Requests remaining in the current one-minute window.
schema:
type: integer
example: 9
RateLimitReset:
description: Unix timestamp, in seconds, when the current rate-limit window resets.
schema:
type: integer
example: 1778520000
RateLimitLimit:
description: Requests allowed in the current one-minute window.
schema:
type: integer
example: 10
schemas:
ErrorResponse:
type: object
required:
- error
- message
properties:
error:
type: string
examples:
- invalid_request
- unauthorized
- rate_limited
message:
type: string
object:
type: string
const: video_job
description: Present on retryable transient video-status errors when the affected API video job is known.
id:
type: string
description: Present on retryable transient video-status errors. API video job ID to keep polling.
credits:
type: object
description: Present on retryable transient video-status errors for accepted API video jobs. Status polls are never billed, so charged is 0.
properties:
charged:
type: integer
minimum: 0
example: 0
remaining:
type:
- integer
- 'null'
minimum: 0
description: Current balance if available; null when the status poll did not fetch credit state.
additionalProperties: false
creation_credits:
$ref: '#/components/schemas/VideoJobCreationCredits'
description: Original create-time video credit debit evidence for the affected API video job when available.
creationCredits:
$ref: '#/components/schemas/VideoJobCreationCredits'
description: SDK-style alias for creation_credits.
retryable:
type: boolean
description: Present on retryable transient errors and non-retryable terminal video-create/status errors so generated clients can identify retry/backoff behavior without parsing details.
retry_blocked_reason:
type: string
description: Present on non-retryable terminal video-create/status errors. Redacted reason explaining why clients should not retry the same request.
retryBlockedReason:
type: string
description: SDK-style alias for retry_blocked_reason.
retry_block_code:
type: string
description: Present on non-retryable terminal video-create/status errors. Stable retry-block code suitable for generated clients.
retryBlockCode:
type: string
description: SDK-style alias for retry_block_code.
retry_after_seconds:
type: integer
minimum: 0
description: Present on retryable transient errors. Mirrors Retry-After and details.retry_after_seconds.
retryAfter:
type: integer
minimum: 0
description: SDK-style alias for retry_after_seconds.
retryAfterSeconds:
type: integer
minimum: 0
description: CamelCase alias for retry_after_seconds.
next_poll_at:
type: string
format: date-time
description: Present on retryable transient errors. Absolute UTC time when clients should retry the request or poll again.
nextPollAt:
type: string
format: date-time
description: SDK-style alias for next_poll_at.
job_id:
type: string
description: Present on retryable transient video-status errors so clients can correlate the retry to the API video job.
jobId:
type: string
description: SDK-style alias for job_id.
task_id:
type: string
description: Task-style alias for job_id on retryable transient video-status errors.
taskId:
type: string
description: CamelCase task-style alias for job_id.
source:
type: string
enum:
- text
- audio
description: Video creation source for the affected API video job.
request_source:
type: string
enum:
- text
- audio
description: Alias for source.
requestSource:
type: string
enum:
- text
- audio
description: SDK-style alias for source.
status_url:
type: string
description: Present on retryable transient video-status errors so clients know which job resource to poll next.
statusUrl:
type: string
description: SDK-style alias for status_url.
created_at:
type: string
format: date-time
description: Creation time for the affected API video job.
createdAt:
type: string
format: date-time
description: SDK-style alias for created_at.
expires_at:
type: string
format: date-time
description: When the affected API video job record expires from status lookup.
expiresAt:
type: string
format: date-time
description: SDK-style alias for expires_at.
details:
description: Endpoint-specific diagnostic fields.
type: object
additionalProperties: true
content_location:
type: string
description: Body alias for the status poll URL, mirroring the Content-Location response header target for clients that cannot reliably read headers.
contentLocation:
type: string
description: SDK-style alias for content_location.
additionalProperties: true
VideoJobCreationCredits:
# --- truncated at 32 KB (42 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/elbo-ai-inc/refs/heads/main/openapi/elbo-ai-inc-audio-uploads-api-openapi.yml