API Documentation

REST API for audio and video transcription. Paid customers authenticate third-party integrations with an API key created in account settings.

Base URL

https://scribe.uustudio.cc

Authentication

Third-party integrations must send a paid account API key in the Authorization header.

Authorization Header

Authorization: Bearer {token}

Create and revoke API keys from the API Keys page in account settings. App authentication endpoints are reserved for first-party clients.

Error Responses

All errors return a JSON body with a message field.

Standard Error

{
    "message": "Description of the error"
}

Validation Error (422)

{
    "message": "The name field is required.",
    "errors": {
        "name": ["The name field is required."]
    }
}
StatusDescription
401Not authenticated (missing or invalid token)
403Forbidden or plan limit exceeded
404Resource not found
422Validation failed
429Rate limited or too many concurrent tasks
500Internal server error

Rate Limits

Authenticated API endpoints are limited to 10 requests per minute per token.

Usage Limits

API availability

Third-party REST API access is not available on the Free plan. Upgrade to Pro or Max, or purchase a Pay as you go package, to create and use API keys.

Max file size
2 GB
Concurrent tasks
Max 3
Max retries per task
3

Paid plan comparison

FeaturePay as you goProMax
Daily uploadsUnlimitedUnlimitedUnlimited
Export formatsAllAllAll
Task priorityMediumMediumHigh

Workflows

Upload Audio/Video File

  1. POST /api/uploads/sign → Get upload_id and part info
  2. PUT {presigned_url} → Upload each part to storage
  3. POST /api/uploads/complete → Finish upload, auto-create task
  4. GET /api/tasks/{id} → Poll status (pending -> scribing -> finished)
  5. GET /api/tasks/{id}/download/srt → Export (optional)

Create Task Manually

  1. POST /api/tasks → Create with disk, path, name
  2. GET /api/tasks/{id} → Poll status
  3. GET /api/tasks/{id} → Get segments, summary, mindmap

Auth

POST /api/login

Authenticate with email and password. Returns a Sanctum token.

Request Body

FieldTypeRequiredDescription
emailstringYesUser email (max 255)
passwordstringYesUser password

Response 200

{
    "token": "1|abc123...",
    "user": {
        "id": 1,
        "name": "Zhang San",
        "email": "[email protected]"
    }
}

Throttled to 5 attempts per minute per email.

POST /api/register

Create a new account. Returns a Sanctum token.

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name (max 255)
emailstringYesUnique email (max 255)
passwordstringYesMust be confirmed
password_confirmationstringYesPassword confirmation

Response 201

{
    "token": "1|abc123...",
    "user": {
        "id": 1,
        "name": "Zhang San",
        "email": "[email protected]"
    }
}
POST /api/auth/social-login

Authenticate via Google OAuth. Creates an account if it does not exist.

Request Body

FieldTypeRequiredDescription
providerstringYesgoogle supported providers
codestringYesOAuth authorization code

Response 200

{
    "token": "1|abc123...",
    "user": {
        "id": 1,
        "name": "Zhang San",
        "email": "[email protected]"
    }
}
POST /api/logout

Revoke the current access token.

Response 204

Returns no body.

Tasks

POST /api/tasks

Create a transcription task. Max 3 concurrent tasks per user.

Request Body

FieldTypeRequiredDescription
diskstringYesr2 / remote / youtube Storage source identifier
namestringYesTask name (max 255)
pathstringYesFile path or source URL
filestringNoOriginal file name (max 255)
sizeintegerNoFile size in bytes
durationintegerNoDuration in seconds
costintegerNoCost in seconds
segmentsarrayNoTranscript segments
speaker_diarizationbooleanNoapp.tasks.speaker_diarization_api_description

Response 201

{
    "id": 1,
    "name": "Meeting Recording",
    "status": "pending",
    "speaker_diarization": true,
    "created_at": "2026-03-02T10:30:00+08:00"
}

Errors

StatusDescription
403Free users cannot submit YouTube links via the API.
429Max 3 concurrent tasks reached
GET /api/tasks/{id}

Get full task details including transcription results.

Response

{
    "id": 1,
    "name": "Meeting Recording",
    "file": "meeting.mp3",
    "path": "2026-03-02/01HQX...mp3",
    "disk": "r2",
    "size": 50545869,
    "duration": 1965,
    "cost": 33,
    "status": "finished",
    "attempts": 1,
    "quality": 3,
    "created_at": "2026-03-02T10:30:00+08:00",
    "updated_at": "2026-03-02T10:45:00+08:00",
    "summary": "The meeting discussed three topics...",
    "mindmap": "# Topic\n## Sub-topic 1",
    "folder": "Work",
    "tags": ["Work"],
    "url": "https://youtube.com/watch?v=...",
    "segments": [
        {
            "id": 1,
            "start": 0,
            "end": 5000,
            "speaker": "Speaker 1",
            "tokens": ["Okay", "so let's", "begin"],
            "text": "Okay, so let's begin..."
        }
    ],
    "export": {
        "allowed_formats": ["txt", "srt", "vtt", "csv", "pdf", "docx"],
        "heavy_formats": ["pdf", "docx"],
        "version": "20260302104500"
    }
}

Response Fields

FieldTypeDescription
diskstring|nullStorage disk: r2 / remote / youtube
sizeint|nullFile size in bytes
costint|nullCost in seconds
attemptsintRetry attempt count
qualityint|nullQuality score (0-5)
summarystring|nullAI-generated summary
mindmapstring|nullAI-generated mind map
folderstring|nullFirst tag name (folder)
tagsarrayAll tag names
urlstring|nullSource URL (for YouTube tasks)
segmentsarray|nullTranscript segments
segments[].startintStart time (ms)
segments[].endintEnd time (ms)
segments[].textstringSegment text
segments[].speakerstring|nullSpeaker identifier
segments[].tokensarray|nullWord token array
exportobjectExport metadata (allowed_formats, heavy_formats, version)
GET /api/tasks

Query Parameters

ParamTypeDefaultDescription
statusstringallFilter by status
searchstring—Search by name
folderstring—Filter by folder (or Uncategorized)
pageint1Page number
per_pageint25Items per page (max 100)

Response

{
    "data": [
        {
            "id": 1,
            "name": "Meeting Recording",
            "file": "meeting.mp3",
            "disk": "r2",
            "size": 50545869,
            "duration": 1965,
            "cost": 33,
            "status": "finished",
            "attempts": 1,
            "quality": 3,
            "folder": "Work",
            "created_at": "2026-03-02T10:30:00+08:00"
        }
    ],
    "current_page": 1,
    "last_page": 1,
    "per_page": 25,
    "total": 1
}
PATCH /api/tasks/{id}/retry

Retry a failed task. Sets status to pending (R2) or waiting (remote/YouTube). Max 3 retries.

Response 200

{
    "id": 1,
    "status": "pending",
    "attempts": 2
}
GET /api/tasks/{id}/download/{format}

Export transcript. Light formats return the file directly. Heavy formats (pdf/docx) may return 202 while processing.

Path Parameters

ParamDescription
formattxt / srt / vtt / csv / pdf / docx

Responses

200 - File ready (light formats)

Returns a binary file.

200 - File ready (heavy formats, JSON request)

{
    "status": "ready",
    "url": "https://scribe.uustudio.cc/api/tasks/1/download/pdf"
}

202 - Processing heavy format

{
    "status": "processing",
    "message": "Your export is being generated. Please try again shortly."
}
GET /api/tasks/{id}/audio

Stream the original audio or video file directly from storage (R2 only).

Response

200 - Binary stream of the audio or video file. Returns 404 if the file is missing or not stored on the R2 disk.

GET /api/tasks/{id}/refresh-summary

Refresh and return the latest AI-generated summary and mind map.

Response

{
    "summary": "The meeting discussed three topics...",
    "mindmap": "# Topic\n## Sub-topic 1"
}
GET /api/tasks/{id}/share-url

Get or generate a public share URL for the task.

Response

{
    "share_url": "https://scribe.uustudio.cc/share/tasks/1"
}
PATCH /api/tasks/{id}

Update task fields. All fields are optional.

Request Body

FieldTypeDescription
namestringNew name (max 255)
folderstring|nullFolder name (null removes the task from a folder)
durationintegerDuration in seconds
costintegerCost in seconds
segmentsarrayFull segment array to replace
textstringFull transcript text
qualityintegerQuality score (0-5)
indexintegerTask order index

Response 200

Returns the full task object (same as Task Detail).

DELETE /api/tasks/{id}

Response 200

{
    "message": "Task deleted successfully"
}
DELETE /api/tasks/bulk

Delete multiple tasks at once. Only tasks belonging to the authenticated user will be deleted. IDs that do not belong to the user are silently ignored.

Request Body

FieldTypeRequiredDescription
idsarrayYesArray of task IDs to delete (min 1)

Response 200

{
    "message": "2 task(s) deleted successfully",
    "deleted_count": 2
}
PATCH /api/tasks/bulk/move

Move multiple tasks to a folder at once. Only tasks belonging to the authenticated user will be moved.

Request Body

FieldTypeRequiredDescription
idsarrayYesArray of task IDs to move (min 1)
folderstringYesTarget folder name (max 255)

Response 200

{
    "message": "2 task(s) moved to Work",
    "moved_count": 2
}

Uploads

Multipart Upload
POST /api/uploads/sign

Request Body

FieldTypeRequiredDescription
filestringYesFile name (must use a supported format)
content_typestringYesMIME type (must match the file extension)
sizeintegerYesFile size in bytes (max 2,147,483,648 / 2 GB)
durationintegerNoAudio duration in seconds

Response

{
    "path": "2026-03-02/01HQX...mp3",
    "upload_id": "abc123",
    "part_size": 5242880,
    "part_count": 10,
    "expires_at": "2026-03-02T10:40:00+08:00"
}
POST /api/uploads/sign-part

Request Body

FieldTypeRequiredDescription
pathstringYesUpload path (max 2048)
upload_idstringYesUpload ID (max 2048)
part_numberintegerYesPart number (1-10000)

Response

{
    "url": "https://storage.example.com/...?X-Amz-...",
    "headers": {
        "Content-Type": "audio/mpeg"
    }
}

Use PUT with the returned URL and headers to upload the part.

POST /api/uploads/complete

Complete a multipart upload. This auto-creates a transcription task and returns 403 when API access is unavailable or a plan limit is exceeded.

Request Body

FieldTypeRequiredDescription
pathstringYesUpload path (max 2048)
upload_idstringYesUpload ID (max 2048)
original_namestringYesOriginal file name (must use a supported format)
content_typestringYesMIME type (must match the file extension)
sizeintegerYesFile size in bytes (max 2 GB)
namestringNoTask name (defaults to the filename without extension)
durationintegerNoDuration in seconds (min 1)
speaker_diarizationbooleanNoapp.tasks.speaker_diarization_api_description

Response 201

{
    "message": "File uploaded to the upload server successfully.",
    "task": {
        "id": 1,
        "name": "meeting",
        "path": "2026-03-02/01HQX...mp3",
        "size": 50545869,
        "status": "pending",
        "speaker_diarization": true
    }
}
POST /api/uploads/abort

Request Body

FieldTypeRequiredDescription
pathstringYesUpload path (max 2048)
upload_idstringYesUpload ID (max 2048)

Response

{
    "message": "Multipart upload cancelled successfully."
}

Folders

Tag-based
GET /api/folders

List all folders with task counts.

Response

{
    "folders": [
        {"id": 1, "name": "Work", "count": 5},
        {"id": 2, "name": "Personal", "count": 3}
    ],
    "uncategorized_count": 2,
    "total_count": 10
}
POST /api/folders

Request Body

FieldTypeRequiredDescription
namestringYesFolder name (max 14 chars / 7 Chinese chars, unique per user)

Response 201

{
    "id": 1,
    "name": "Work"
}
PATCH /api/folders/{name}

Request Body

FieldTypeRequiredDescription
namestringYesNew folder name (same constraints as create)

Response 200

{
    "id": 1,
    "name": "New Name"
}
DELETE /api/folders/{name}

Response 200

{
    "message": "Folder deleted successfully"
}

User

GET /api/user

Response

{
    "id": 1,
    "name": "Zhang San",
    "email": "[email protected]",
    "avatar": "https://...",
    "balance": {
        "used_seconds": 9000,
        "remaining_seconds": 36000,
        "allowance_remaining_seconds": 30000,
        "top_up_remaining_seconds": 6000,
        "total_seconds": 45000,
        "usage_percent": 20
    },
    "subscription": {
        "plan": "year",
        "plan_key": "max_yearly",
        "tier": "max",
        "billing_interval": "yearly",
        "active": true,
        "access_tier": "max"
    },
    "queue": {
        "pending_count": 1,
        "running_count": 1,
        "failed_count": 0
    }
}

Balance Fields

FieldTypeDescription
used_secondsintSeconds used since the last monthly grant
remaining_secondsintRemaining seconds
total_secondsintTotal seconds
usage_percentintUsage percentage (0-100)
PUT /api/user

Request Body

FieldTypeRequiredDescription
namestringNoDisplay name (max 255)
emailstringNoNew email (must be unique)
avatarfileNoImage file (max 5 MB)

Response 200

{
    "id": 1,
    "name": "New Name",
    "email": "[email protected]",
    "avatar": "https://..."
}

Notifications

GET /api/notifications

Get the 10 most recent notifications.

Response

[
    {
        "id": "abc123",
        "title": "Task Completed",
        "message": "Meeting Recording has been transcribed.",
        "read_at": null,
        "created_at": "2026-03-02T10:45:00+08:00"
    }
]
POST /api/notifications/mark-all-as-read

Mark all unread notifications as read.

Response 200

{
    "success": true
}

Checkout

GET /api/checkout/{plan}

Get a Paddle checkout URL for a subscription plan.

Path Parameters

ParamDescription
plantest / month / year

Response

Returns a Paddle checkout options object. Returns 404 for an invalid plan.

GET /api/health

Public health check endpoint. No authentication required.

Response 200

{
    "status": "ok"
}