> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://onepin.ai/docs/guides/upload-a-file/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://onepin.ai/_mcp/server. # Upload a file Use an uploaded file as a workflow's script instead of inline text. Uploads use a presigned-URL flow (create → PUT bytes → confirm) and require the `uploads:write` scope. > **Note** > > Categories: `script` (`.txt`, `.pdf`) or `dictionary` (pronunciation reference audio: `.mp3`, `.wav`, `.m4a`, `.ogg`, `.webm`). #### Python ```python import httpx from onepin import OnepinClient client = OnepinClient() # 1. create the upload record and get a presigned URL created = client.uploads.create(filename="script.txt", category="script").data upload_id, upload_url = created.upload.id, created.upload_url # 2. PUT the bytes straight to storage (not via the SDK) with open("script.txt", "rb") as f: httpx.put(upload_url, content=f.read()).raise_for_status() # 3. confirm and bind it to a workflow confirmed = client.uploads.confirm( upload_id, context_type="workflow", context_id=workflow_id ).data print(confirmed.char_count, confirmed.line_count, confirmed.detected_language) ``` #### CLI ```bash # create + PUT bytes in one step; prints the upload id onepin uploads create --file script.txt --category script onepin uploads confirm "$UPLOAD_ID" --workflow-id "$WORKFLOW_ID" ``` Remove one with `onepin uploads delete "$UPLOAD_ID" --yes`. ## What confirm gives you back For a `script` `.txt` / `.pdf` upload, confirming fills in three fields worth reading: | Field | Meaning | | ------------------- | ------------------------------------------------------------------------------------------------- | | `char_count` | Decoded character count — the sum of the stripped, non-empty lines. This is what you're billed on | | `line_count` | Number of non-empty lines | | `detected_language` | The dominant source language the server detected, as a bare code (`en`, `ko`) | `detected_language` is a **bare** language, not a locale — map it to a supported regional locale before you put it in a node config (`en` → `en-us`). It's null for `dictionary` uploads and whenever detection didn't run. ## Related * [Script](/docs/workflow/script) — pointing a script node at an upload > Onepin is a voice workflow platform that orchestrates, validates, and ships production-ready audio across 33 TTS models.