Skip to main content

Documentation Index

Fetch the complete documentation index at: https://apidocs.scripe.io/llms.txt

Use this file to discover all available pages before exploring further.

Sources

A source is a transcribed file uploaded into a project — typically a voice memo, podcast snippet, or article import. Sources feed the AI pipeline that produces notes and post drafts; they’re not directly visible to end users in the published feed. The v1 API ships a single-resource read endpoint for sources. There is no list endpoint in v1; if you need to enumerate sources, traverse notes (GET /v1/notes) and follow each note’s source attachment when it ships in v1.x.

GET /v1/sources/{sourceId}

Read a single source row. The transcription body is truncated to the first 2000 characters for safety — long-form transcripts are excluded from the API surface, intentionally:
  • Sources can hold customer audio they uploaded with no expectation it would leave the dashboard. Exposing the full body would be a surprise.
  • Some sources are large (multi-megabyte transcripts of hour-long calls). Pulling them through a JSON response would amplify rate-limit usage without any real client benefit.
If you need the full transcript for a programmatic workflow, file a feature request and we’ll consider a streaming download endpoint in Phase 4.
curl -i https://api.scripe.io/v1/sources/src_01J9ZA… \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "id": "src_01J9ZA…",
    "projectId": "proj_01J9ZA…",
    "status": "Done",
    "name": "founder-podcast-2026-05-15.m4a",
    "fileType": "audio/mp4",
    "durationSeconds": 482,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": "2026-05-15T08:23:11.000Z",
    "textPreview": "Hi everyone, today I want to talk about how we …",
    "textPreviewTruncated": true,
    "hasFullText": true
  }
}

Field reference

PathTypeNotes
idstring (src_*)
projectIdstring (proj_*)The owning project.
statusstringProcessing | Done | Failed. Treat unknown values as Processing.
namestring | nullOriginal filename if known.
fileTypestring | nullMIME type of the upload (best-effort; sometimes inferred).
durationSecondsinteger0 for non-audio sources (text imports, etc.).
createdAtstring (ISO 8601)
updatedAtstring | nullnull if never updated since creation.
textPreviewstringFirst 2000 chars of the transcript. May be empty if status !== "Done" or for sources without a transcript yet.
textPreviewTruncatedbooleantrue if the original transcript exceeded 2000 chars.
hasFullTextbooleantrue if a transcript exists at all (regardless of whether the preview is truncated). Use this to know whether processing has finished.
The S3 storage keys, internal upload identifiers, the original processing-flusher state, and the secret presigned URLs are intentionally not in this response. We never return them through the public API.

Truncation contract

  • textPreviewTruncated === true → the preview is exactly 2000 characters and there is more transcript on the server.
  • textPreviewTruncated === false AND hasFullText === true → the preview is the full transcript.
  • hasFullText === false → the source has no transcript yet (still processing or processing failed). textPreview will be empty.
We do not truncate at character boundaries that could split UTF-8 codepoints in the middle of a multi-byte sequence — the implementation slices safely at the codepoint level. Your client can render the preview without sanitisation.

Errors

StatusCode
401unauthenticated, invalid_token, key_revoked, key_expired
403scope_missing (need notes:read — sources share the notes scope), plan_not_eligible
404not_found
429rate_limited

POST /v1/sources

Create a source. Required scope: sources:write. Two input shapes:
  • type: "text" — synchronous. The caller supplies the prepared transcript and the row is stored with status: Success immediately. Returns the new Source envelope (200).
  • type: "file" — asynchronous. The caller first calls POST /v1/uploads to obtain a presigned S3 PUT URL + opaque uploadId handle, PUTs the bytes, then references the handle here. Returns a Job envelope (200) with type: SOURCE_INGEST_FILE; poll GET /v1/jobs/{jobId} until status: COMPLETED and the resulting result.sourceId becomes available.
curl -i https://api.scripe.io/v1/sources \
  -X POST \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a-unique-id-from-your-side" \
  -d '{
    "type": "text",
    "projectId": "proj_01J9ZA…",
    "text": "Today we shipped the new pricing model…",
    "name": "Stand-up summary 2026-05-15"
  }'

Request body — type: "text"

FieldTypeRequiredNotes
typestringyes"text".
projectIdstring (proj_*)yesMust belong to the workspace; otherwise 404 not_found.
textstringyesUp to 1 MB. Empty string is rejected with 400 invalid_request.
namestring | nullnoOptional human label.

Request body — type: "file"

FieldTypeRequiredNotes
typestringyes"file".
projectIdstring (proj_*)yesMust belong to the workspace; otherwise 404 not_found.
uploadIdstring (upl_*)yesReturned by POST /v1/uploads. The handle is bound to the workspace; using one from another workspace returns 404 not_found.
namestring | nullnoOptional human label. Defaults to the bucket key tail.

Response — type: "text"

HTTP/1.1 200 OK
Content-Type: application/json
Idempotent-Replayed: false

{
  "data": {
    "id": "src_01J9ZA…",
    "projectId": "proj_01J9ZA…",
    "status": "Success",
    "name": "Stand-up summary 2026-05-15",
    "fileType": null,
    "durationSeconds": 0,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": null,
    "textPreview": "Today we shipped the new pricing model…",
    "textPreviewTruncated": false,
    "hasFullText": true
  }
}

Response — type: "file"

HTTP/1.1 200 OK
Content-Type: application/json
Idempotent-Replayed: false

{
  "data": {
    "id": "job_01J9ZB…",
    "type": "SOURCE_INGEST_FILE",
    "status": "QUEUED",
    "projectId": "proj_01J9ZA…",
    "startedAt": null,
    "completedAt": null,
    "progress": null,
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "attemptCount": 0,
    "estimatedCompletionMs": 240000,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": null
  }
}
Once the worker finishes:
{
  "id": "job_01J9ZB…",
  "status": "COMPLETED",
  "result": { "sourceId": "src_01J9ZA…" },
  ...
}
GET /v1/sources/{sourceId} then returns the fully-processed source. The async path also enforces the per-plan AI spend cap; calls that would push the daily cost over the cap return 402 spend_cap_exceeded without enqueueing a job. See docs/api/v1/jobs.md for the full lifecycle and pricing.

Errors

StatusCode
400invalid_request (missing projectId/type, empty text, missing uploadId for type: "file")
401unauthenticated, invalid_token, key_revoked, key_expired
402spend_cap_exceeded (file branch only — daily AI spend cap reached)
403scope_missing (need sources:write), plan_not_eligible
404not_found (project not in workspace; or uploadId belongs to another workspace)
409idempotency_key_conflict
413payload_too_large (text > 1 MB)
422unprocessable (e.g. unknown type value)
429rate_limited

What’s NOT here (yet)

  • List endpoint. No GET /v1/sources. Plan-side decision; if you have a use case, file a feature request.
  • Full transcript download. See above.
  • File / audio download. Storage URLs are private. The dashboard’s download links are signed and time-limited; we won’t expose them via the API in v1.
  • Update / delete endpoints. PATCH / DELETE are out of scope for v1.