A file is uploaded first and then saved on its record by that record's own endpoint: a mix's audio_file_id, a post's media_file_ids, a message's file_ids, or a booking document. An upload that no record uses is removed after one day. Profile pictures are the exception: a user_avatar upload becomes the person's profile picture immediately.
File scopes
Every upload names its file scope, which determines the accepted types, the size limit and the OAuth scope required.
| OAuth scope | File scopes |
|---|---|
profile:write | user_avatar, user_cover |
content:write | post_media, mix_audio, mix_cover, live_event_cover, live_event_flyer |
inbox:write | message_attachment, message_voice |
bookings:write | proposal_document |
SignaturesSignatures and signed PDFs (
proposal_signature,proposal_pdf) cannot be uploaded by an application. Documents are signed on DJfindr.
Upload methods
| Method | Use for | Endpoint |
|---|---|---|
| Single request | Files up to 100 MB | POST /me/files |
| Multipart | Larger files, such as mix audio | POST /me/files/multipart, then each part, then complete |
| Import from a URL | Clients that cannot send binary data | POST /me/files/import |
Single request
The request body is the file itself, not a form, with its exact length in Content-Length. The file scope, the SHA-256 of the bytes as sha256 (required with an Idempotency-Key; see Making changes), and optionally name and type, are query parameters. The stored type is determined from the bytes: if you name a type and the file is something else, the upload is refused.
curl -X POST "https://api.djfindr.com/prod/v1/me/files?scope=mix_cover&name=cover.png&sha256=$(shasum -a 256 cover.png | cut -d' ' -f1)" \
-H "Authorization: Bearer djf_at_..." \
-H "Idempotency-Key: 1b7e..." \
-H "Content-Type: application/octet-stream" \
--data-binary @cover.pngMultipart
POST /me/files/multipartwith{ "scope", "size", "type", "name" }. The response contains the file, itspart_sizeand the number ofparts.- Send each part as the body of
PUT /me/files/multipart/{id}/parts/{n}, withnstarting at 1. Every part except the last must be exactlypart_sizebytes. Keep theetagfrom each response. POST /me/files/multipart/{id}/completewith{ "parts": [{ "part_number": 1, "etag": "..." }] }, listing every part.
To abandon a multipart upload, call DELETE /me/files/multipart/{id}.
Import from a URL
POST /me/files/import with { "scope", "url" } fetches a public https:// address and stores it as an upload, under the same rules as a direct upload.
Downloading
GET /me/files/{id} returns the file and its download address:
| File | download |
|---|---|
| Public | The file's permanent address. |
| Private (a message attachment, a booking document) | A signed address valid for 15 minutes. It works without a session or credentials, from a browser, a server or a fetch() from any origin (CORS allows every origin; no cookie is sent or set). |
Signed addressesTreat a signed address as a secret while it is valid. The response is never cached. An address can stop working early, when the file is deleted or replaced or DJfindr rotates its signing key; request the file again for a new address.
Deleting
DELETE /me/files/{id} deletes one of the person's uploads that no record uses: an upload still waiting to be saved, or a picture that is not in use. A file saved on a record, or in use as the person's picture, returns 409; remove it from the record instead.