Skip to main content

Sources

A source is long-form raw input for the content pipeline — a voice memo, podcast episode, webinar recording, or pasted transcript. After ingest, Scripe extracts topics — scored chapters of the material — and a topic’s id is what you feed to post generation. Scopes: sources:read / sources:write — grantable to API keys and OAuth tokens alike (see auth.md §1.2). Deleting rides sources:destroy, which is OAuth-only and never implied by any other scope. Full schemas: OpenAPI reference → Sources.

Reading sources

A source row:
  • status is Processing (ingest in flight), Success (transcript + topics ready), or Error. Older rows may carry the legacy terminal value Done. Treat unknown values as Processing.
  • textPreview is the first 2,000 characters of the transcript — the full body is deliberately not exposed over the API (sources can hold customer audio uploaded with no expectation it would leave the dashboard, and hour-long transcripts would dwarf every other response). textPreviewTruncated and hasFullText tell you which case you’re in. The preview slices at codepoint boundaries — safe to render as-is.
  • Storage keys and presigned URLs are never returned.

Topics

GET /v1/sources/{sourceId} includes a topics array once status is "Success" — best-first by score, each with title, summary, keyTakeaways, and where it sits in the recording:
  • score is the quality signal; ranking is just the position in the served order (ranking: 1 is always topics[0]). fromSeconds says where the topic sits in the recording — an earlier chapter is not a better one.
  • hooks is empty for anything ingested through this API. Hooks are generated by the Scripe dashboard when a human opens a topic; nothing on the API path creates them, and you don’t need one — post generation writes its own.
The intended flow: ingest → present topics to your user → the user picks → feed the topic id into POST /v1/posts/generations as source: { "type": "topic", "topicId": "tpc_…" }. The server reads that topic’s transcript chunk itself — the full transcript is deliberately never served over this API, so the topic id is the only way to generate from what the user actually said rather than from the summary about it. The list endpoint stays cheap and omits topics; fetch the single source for them.

POST /v1/sources

Two input shapes: type: "text" — synchronous. You supply the prepared transcript (1 byte – 1 MB); the row is stored with status: "Success" immediately and the response is the Source envelope.
type: "file" — asynchronous. First mint an upload handle via POST /v1/uploads, PUT your bytes to the signed URL, then reference the handle here. The response is a Job envelope with type: "SOURCE_INGEST_FILE"; poll until status: "DONE", then result.sourceId names the new source (whose own status tracks transcription).
An uploadId minted in another workspace returns 404 not_found — handles are workspace-bound. The file branch is metered by the AI budget (402 when exhausted). The uploaded file is not kept. Once the job reaches DONE the transcript, topics and generated content live on the source; the original audio, video or document is deleted from storage as part of processing. For an audio or video source the dashboard shows the transcript without a player, noting that API uploads are not retained; a document source shows the transcript with no player and no note. Keep your own copy if you need the original. MCP hosts can skip the two-step upload for small files: the create_source_file tool takes the bytes inline as base64 up to ~3 MB decoded. A real audio or video recording is far past that, so it comes through this flow.

DELETE /v1/sources/{sourceId} — two-phase

Permanently deletes a source: the row, the full transcript (paragraphs and sentences), its topics and their hooks, its knowledge-base copy (document, chunks, embeddings — knowledge search stops returning the content immediately), and the stored audio/video/file object, whose storage quota is released. Irreversible. A bare DELETE deletes nothing. It returns a proposal (data.phase: "proposal") naming exactly what will be removed and what survives, plus a confirmationToken (~5-minute TTL). Show it to the user; after they explicitly confirm, repeat the request with ?confirmationToken= to execute. The token is bound to this source and this principal — a mismatched or expired token fails 400 invalid_request (details.reason: confirmation_invalid / confirmation_expired) and deletes nothing.
This is deliberately not total erasure, and both phases say so in their kept block:
  • Posts generated from the source are kept. They are the user’s content; only their link to the source goes stale. Removing one is DELETE-less today — use delete_post over MCP.
  • The internal usage-accounting ledger survives — it is historical cost reporting, not source data.
If any knowledge-base document written from this transcript was shared with the whole company, the delete additionally requires an admin of the workspace that document is shared with — which is not always your own, since moving a project between workspaces leaves its company-shared documents behind. That is the same rule delete_knowledge_doc applies to those rows, enforced in both phases. Without it the call fails 403 admin_required and nothing is deleted; the proposal counts them in data.toDelete.workspaceSharedKnowledgeDocuments and warns about them. Executing emits source.deleted with the source’s identity only — never the transcript. Requires sources:destroy (OAuth-only; sources:write and the write alias never imply it). On MCP the same verb is the two-phase delete_source tool.

What’s NOT here (yet)

  • Full transcript download — previews only, by design.
  • File / audio download — storage URLs are private.
  • Update — not exposed on any surface today.