Files

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 scopeFile scopes
profile:writeuser_avatar, user_cover
content:writepost_media, mix_audio, mix_cover, live_event_cover, live_event_flyer
inbox:writemessage_attachment, message_voice
bookings:writeproposal_document
📘

Signatures

Signatures and signed PDFs (proposal_signature, proposal_pdf) cannot be uploaded by an application. Documents are signed on DJfindr.

Upload methods

MethodUse forEndpoint
Single requestFiles up to 100 MBPOST /me/files
MultipartLarger files, such as mix audioPOST /me/files/multipart, then each part, then complete
Import from a URLClients that cannot send binary dataPOST /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.png

Multipart

  1. POST /me/files/multipart with { "scope", "size", "type", "name" }. The response contains the file, its part_size and the number of parts.
  2. Send each part as the body of PUT /me/files/multipart/{id}/parts/{n}, with n starting at 1. Every part except the last must be exactly part_size bytes. Keep the etag from each response.
  3. POST /me/files/multipart/{id}/complete with { "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:

Filedownload
PublicThe 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 addresses

Treat 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.