Skip to content

Start an upload ​

POST /api/v1/uploads uploads:write

Starts the upload of a recording, for example a Voice Memos file. This is step 1 of 3. The file itself never goes through this API (a request body is capped at 4.5 MB); it goes straight to storage.

The three steps

  1. POST /uploads with the file name. The answer holds uploadUrl and completeUrl.
  2. PUT the raw bytes of the file to uploadUrl. The body is the file and nothing else: no multipart form and no Authorization header, because the token is part of the URL. A Content-Type such as audio/mp4 is fine and optional.
  3. POST to completeUrl with your key. Protoki checks that the file arrived and starts the transcription and the minutes.
bash
# 1. start
curl -X POST https://protoki.com/api/v1/uploads \
  -H "Authorization: Bearer $PROTOKI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename": "Voice Memo 12.m4a", "title": "Site visit hall 3", "consentConfirmed": true}'

# 2. upload the file to "uploadUrl" from the answer
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: audio/mp4" \
  --data-binary @"Voice Memo 12.m4a"

# 3. finish, at "completeUrl" from the answer
curl -X POST "$COMPLETE_URL" \
  -H "Authorization: Bearer $PROTOKI_API_KEY"

In the Shortcuts app the same three steps are three actions. "Get Contents of URL" with method POST, header Authorization: Bearer pk_live_your_key and a JSON request body (filename, consentConfirmed as true) gives you the answer; take uploadUrl and completeUrl out of it with "Get Dictionary Value". A second "Get Contents of URL" with method PUT, the URL uploadUrl and the request body set to File, with the Voice Memos recording as the file, uploads it. A third one with method POST, the URL completeUrl and the same header finishes it.

Consent. consentConfirmed is your statement that everyone in the recording knows it was made. It is required, and Protoki records it with the meeting and the person the key belongs to.

Who may upload. Only a personal key that carries uploads:write. The person needs a recording licence, exactly as in the app, and the meeting belongs to that person. An account key has no person and answers 403. Processing a recording costs transcription quota, which is why the scope is never on by default.

Limits. One file of at most 400 MB, in one of these formats: .wav, .mp3, .m4a, .flac, .ogg, .webm. There is no length limit: a recording longer than one transcription request is split on the server. uploadUrl is valid for 2 hours and takes one file once; to try again, start over with a new POST /uploads.

Afterwards the minutes are written in the background. Read GET /meetings/{id} until status is ready, or use the meeting.ready webhook.

Body ​

Example

json
{
  "filename": "Voice Memo 12.m4a",
  "consentConfirmed": true,
  "title": "Site visit hall 3",
  "templateId": "5a2e8b47-9c31-4d6f-a0b8-1e7f3c5d2049"
}
FieldTypeRequiredDescription
filenamestringyesName of the file you are going to upload. Its extension decides the type, in any case: .wav, .mp3, .m4a, .flac, .ogg, .webm. A Voice Memos recording from an iPhone is .m4a.
consentConfirmedtrueyesYour statement that everyone in the recording knows it was made. It must be true; without it the call answers 400. Protoki keeps the confirmation with the meeting.
titlestring—Title of the meeting. Leave it out and Protoki derives one from the conversation after the transcription; the file name stands in until then.
templateIduuid—The minutes template to use. It must belong to your workspace. Leave it out and the usual default applies.

Response 201 ​

The meeting exists and the upload URL is ready.

Example response

json
{
  "meetingId": "c4d9f0a1-72be-4e35-8f10-6d2a5b8c9e04",
  "uploadUrl": "https://storage.example/object/upload/sign/audio/…/original.m4a?token=…",
  "completeUrl": "https://protoki.com/api/v1/uploads/…/complete",
  "expiresInSeconds": 7200
}
FieldTypeRequiredDescription
meetingIduuidyesThe meeting that was created. Identifies it everywhere in this API.
uploadUrluriyesWhere to PUT the file: the raw bytes as the request body, nothing else. The URL carries its own token, so send no Authorization header.
completeUrluriyesWhere to POST after the upload, with your API key. Step 3.
expiresInSecondsintegeryesHow long uploadUrl stays valid, counted from this answer.

Other answers ​

CodeMeaning
400The body is invalid, the consent is missing, the file type is not supported or the template does not exist. grund.code says which.
401No key, or a key that is not valid.
403The key lacks uploads:write, is an account key (accountKeyNoOwner), belongs to another workspace than its person (keyWorkspaceMismatch), or the person has no recording licence (seatRequired).

Protoki — EU/GDPR-compliant meeting notetaker.