API reference

Store, list, move, share and serve files from your own program. Everything here is made from the API's own definition of itself, so it is what the API does.

Endpoints on this page

Every address below begins with https://api.filefreak.io.

Send the key on every request: Authorization: Bearer ffk_live_... A key is a password for the app. Keep it on a server, never in a web page or an app somebody can take apart.

Make an app and a key in Developers. A key is given some of these scopes, and can be kept to one folder of its app: files:read, files:write, files:delete, usage:read.

A file's bytes never pass through the API. To upload, the API gives addresses and the bytes go straight to storage; to download, it gives an address on storage. That is why a file of any size can be sent by anything that can make a PUT request, including a browser that has never seen your key.

Where an endpoint says it honours one, send an Idempotency-Key header, any string of your own that is unique to the thing you are trying to do. The same request sent again with the same key is given the first answer and makes nothing twice. Keys are remembered for a day.

One key may make 600 calls a minute. Images can be asked for at 64, 128, 256, 512, 1024 pixels.

Files and folders

List a folder

GET/v1/files

Needs the scope files:read.

What is in a folder, a page at a time: folders first, then files, each by name. Pass nextCursor back as cursor for the next page; when it is null there is no more. Things in the trash are not listed. A page never repeats or skips something because of what was added or removed between two requests.

In the query

parentidThe folder to list. Left out, the top of the app (or of the folder the key is confined to).
cursorstringnextCursor from the page before.
limitintegerHow many to return, 1 to 200. Left out: 50.

200 Always.

Answers with

itemsrequiredlist of object
items[].idrequiredidNever changes, whatever is renamed or moved.
items[].kindrequiredone of: file, folder
items[].namerequiredstring
items[].parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
items[].sizeBytesrequiredintegerZero for a folder.
items[].contentTyperequiredstring, or nullNull for a folder.
items[].visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
items[].publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
items[].trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
items[].createdAtrequiredstring
items[].updatedAtrequiredstring
nextCursorrequiredstring, or null

One file or folder

GET/v1/files/:id

Needs the scope files:read.

By its id. Something in the trash is still returned, with trashedAt set.

In the address

idrequiredid

200 It exists and the key can reach it.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

Rename, move, or make public

PATCH/v1/files/:id

Needs the scope files:write.

Any of the three, in one request. They are applied in that order, each as its own change: if the second is refused the first has still happened, and the answer says which was refused. Moving a folder moves everything in it and is one change however much that is. Making a folder public makes everything in it public.

In the address

idrequiredid

In the body, as JSON

namestringA new name. Refused with name_taken if the folder already has something called that.
parentIdid, or nullThe folder to move it into, or null for the top of the app.
visibilityone of: private, public

200 Every change asked for was made.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

Put in the trash, or delete for good

DELETE/v1/files/:id

Needs the scope files:delete.

Without permanent, the file or folder goes to the trash: it can be restored, it still counts toward storage, and it is removed for good after thirty days. With permanent=true it is removed now, with everything inside it, and cannot be brought back.

In the address

idrequiredid

In the query

permanentone of: true, falsetrue to delete for good instead of putting in the trash. Left out: "false".

200 It is in the trash, or gone.

Answers with

okrequiredtrue

Take out of the trash

POST/v1/files/:id/restore

Needs the scope files:write.

It goes back where it was. If that folder has gone it goes to the top, and if its name has been taken in the meantime it comes back with a number after it.

In the address

idrequiredid

200 It is out of the trash.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

Make a folder

POST/v1/folders

Needs the scope files:write. Honours an Idempotency-Key.

Folders can be nested up to 32 deep. With ifExists=use, a folder of that name that is already there is returned and nothing is made, which is what a program that makes its folders on every start wants.

In the body, as JSON

namerequiredstring
parentIdid, or nullThe folder to make it in. Null, or left out, for the top of the app. Left out: null.
ifExistsone of: fail, useWhat to do if the folder already has one of that name. Left out: "fail".

201 A folder was made.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

200 ifExists was use, and one was already there.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

Uploading

Begin an upload

POST/v1/uploads

Needs the scope files:write. Honours an Idempotency-Key.

Says what is about to be sent, and answers with where to send it. A file's bytes never pass through this API: they go straight to storage, in parts, each with a plain PUT to the address given for it. The first addresses come back here; ask for the rest as they are needed. Then complete the upload, and it becomes a file.

The room is set aside now, so an upload that would not fit is refused before a byte is sent. The parts can be sent by anything that can make a PUT request, including a browser that has never seen the API key: hand it the addresses.

In the body, as JSON

namerequiredstring
sizeBytesrequiredintegerExactly how many bytes will be sent. What arrives is measured, and that is what is counted.
contentTypestringLeft out, it is worked out from the name.
parentIdid, or nullThe folder to put it in. Null, or left out, for the top of the app. Left out: null.
onConflictone of: rename, replace, failWhat to do if the folder already has a file of that name. replace keeps the file's id and its public address. Left out: "rename".
imageSizeslist of integerSizes to make of it, if it is an image, so they are ready when wanted: any of 64, 128, 256, 512, 1024.

201 The upload has begun.

Answers with

uploadrequiredobject
upload.idrequiredid
upload.namerequiredstring
upload.parentIdrequiredid, or null
upload.sizeBytesrequiredinteger
upload.contentTyperequiredstring, or null
upload.statusrequiredone of: pending, completed, aborted, expired
upload.onConflictrequiredone of: rename, replace, fail
upload.partBytesrequiredintegerEvery part is this many bytes, except the last, which is whatever remains.
upload.partCountrequiredinteger
upload.fileIdrequiredid, or nullThe file this became, once it has.
upload.expiresAtrequiredstringThe upload has to be completed by then.
upload.createdAtrequiredstring
partsrequiredlist of object
parts[].numberrequiredintegerCounted from zero.
parts[].methodrequired"PUT"
parts[].urlrequiredstringSend this part's bytes here, as the body. No API key: the address is the permission.
parts[].headersrequiredvalueHeaders the request must carry.
parts[].expiresAtrequiredstringWhen this address stops working. Ask for the parts again to get fresh ones.

An upload, and which parts have arrived

GET/v1/uploads/:id

Needs the scope files:write.

For carrying on after an interruption: storage itself says which parts it has, so nothing the client remembered is needed.

In the address

idrequiredid

200 Always.

Answers with

uploadrequiredobject
upload.idrequiredid
upload.namerequiredstring
upload.parentIdrequiredid, or null
upload.sizeBytesrequiredinteger
upload.contentTyperequiredstring, or null
upload.statusrequiredone of: pending, completed, aborted, expired
upload.onConflictrequiredone of: rename, replace, fail
upload.partBytesrequiredintegerEvery part is this many bytes, except the last, which is whatever remains.
upload.partCountrequiredinteger
upload.fileIdrequiredid, or nullThe file this became, once it has.
upload.expiresAtrequiredstringThe upload has to be completed by then.
upload.createdAtrequiredstring
uploadedPartsrequiredlist of integerThe numbers of the parts storage has.

Addresses for more parts

GET/v1/uploads/:id/parts

Needs the scope files:write.

Addresses are given out a window at a time and each lasts half an hour. Ask again for any that have run out.

In the address

idrequiredid

In the query

fromintegerThe first part wanted, counted from zero. Left out: 0.
countintegerLeft out: 16.

200 Always.

Answers with

partsrequiredlist of object
parts[].numberrequiredintegerCounted from zero.
parts[].methodrequired"PUT"
parts[].urlrequiredstringSend this part's bytes here, as the body. No API key: the address is the permission.
parts[].headersrequiredvalueHeaders the request must carry.
parts[].expiresAtrequiredstringWhen this address stops working. Ask for the parts again to get fresh ones.

Finish an upload

POST/v1/uploads/:id/complete

Needs the scope files:write.

Turns the parts into a file. Safe to call again: a second call for an upload that has finished answers with the same file. For a very large file storage may still be putting it together, and the answer is then 202 with how long to wait before asking again.

In the address

idrequiredid

200 The file exists.

Answers with

itemrequiredobject
item.idrequiredidNever changes, whatever is renamed or moved.
item.kindrequiredone of: file, folder
item.namerequiredstring
item.parentIdrequiredid, or nullThe folder it is in. Null at the top of the app.
item.sizeBytesrequiredintegerZero for a folder.
item.contentTyperequiredstring, or nullNull for a folder.
item.visibilityrequiredone of: private, publicIts own setting. A private file inside a public folder is still public.
item.publicUrlrequiredstring, or nullAn address anybody can fetch, that does not expire, while the file is public. Null for a folder and for anything that is not public.
item.trashedAtrequiredstring, or nullWhen it was put in the trash, if it is there.
item.createdAtrequiredstring
item.updatedAtrequiredstring

202 Storage is still putting it together. Ask again after retryAfterSeconds.

Answers with

pendingrequiredtrue
retryAfterSecondsrequiredinteger

Cancel an upload

DELETE/v1/uploads/:id

Needs the scope files:write.

Whatever had arrived is deleted, and the room it held comes back at once.

In the address

idrequiredid

200 It is cancelled.

Answers with

okrequiredtrue

Downloading

An address to download a file from

GET/v1/files/:id/download

Needs the scope files:read.

A short-lived address on storage itself. Anybody holding it can fetch the file until it expires, with no key, so it can be handed to a browser. Each one issued counts the file's size toward the month's downloads. For an address that does not expire, make the file public and use its publicUrl.

In the address

idrequiredid

In the query

inlineone of: true, falsetrue to have a browser show it, for the types that are safe to show. Anything else is saved, whatever is asked. Left out: "false".

200 Always.

Answers with

urlrequiredstring
expiresAtrequiredstring
inlinerequiredtrue or falseWhether the address will show the file or save it.
contentTyperequiredstring, or null

An image at a smaller size

GET/v1/files/:id/image

Needs the scope files:read.

A copy of an image no more than size pixels on its longest side, as WebP, with the camera's metadata removed. The sizes are 64, 128, 256, 512, 1024 and no others. Each is made once, the first time it is asked for, and kept: until it has been made the answer is 202. Name the sizes when the image is uploaded and they are ready by the time they are wanted. For a public file, add ?size= to its publicUrl.

In the address

idrequiredid

In the query

sizerequiredintegerOne of 64, 128, 256, 512, 1024.

200 That size has been made.

Answers with

statusrequired"ready"
urlrequiredstring
widthrequiredinteger
heightrequiredinteger
expiresAtrequiredstring

202 It is being made. Ask again after retryAfterSeconds.

Answers with

statusrequired"pending"
retryAfterSecondsrequiredinteger

Usage

What the app is using

GET/v1/usage

Needs the scope usage:read.

What this app holds, against the ceiling set for it if there is one; and what the account it belongs to has used this calendar month, across its personal files and all its apps, against what its plan includes. An app's files count toward the account's one storage allowance.

200 Always.

Answers with

apprequiredobject
app.storedBytesrequiredinteger
app.fileCountrequiredinteger
app.storageCapBytesrequiredinteger, or nullThe ceiling set for this app, or null for none.
app.apiCallsThisMonthrequiredinteger
accountrequiredobject
account.planrequiredstring
account.storageUsedBytesrequiredinteger
account.storageIncludedBytesrequiredinteger
account.storageLimitBytesrequiredintegerWhere uploads are refused: what the plan includes, plus what the account's spending limit allows beyond it.
account.downloadedBytesThisMonthrequiredinteger
account.downloadsIncludedBytesrequiredinteger
account.apiCallsThisMonthrequiredinteger
account.apiCallsIncludedrequiredinteger

Refusals

Every refusal is JSON: { error, message }. `error` is a short code that does not change, and is what a program should branch on. `message` is a sentence for a person, and may be reworded.

StatuserrorWhat it means
400invalid_requestSomething sent was missing or the wrong shape. `field` names it.
400invalid_cursorThe cursor is not one this API gave out.
400unknown_sizeAn image was asked for at a size that is not on the list. `sizes` is the list.
400size_mismatchWhat arrived for an upload is not the size that was announced. Nothing was kept.
400not_a_folderA file was named where a folder was wanted.
401missing_keyNo API key was sent.
401invalid_keyThe key is not one this service made.
401key_revokedThe key has been revoked.
401key_expiredThe key has passed its expiry.
402plan_requiredThe account is not on Pro. The developer API is part of Pro.
402plan_endedPro has ended for the account. Its keys can only read, until readOnlyUntil.
402spending_limitBeyond what the plan includes, and paying for it would pass the account's spending limit.
402download_limitThe month's download allowance is used.
403insufficient_scopeThe key is not allowed to do that.
403confined_keyThe key is for one folder, and that is the folder itself or covers the whole app.
403app_disabledThe app is switched off.
403account_suspendedThe account the key belongs to is suspended.
404not_foundThere is no such file, folder or upload that this key can reach.
409name_takenThe folder already has something of that name.
409request_in_progressA request with the same Idempotency-Key is still being answered.
409upload_completedThe upload has already become a file, and cannot be cancelled.
410upload_abortedThe upload was cancelled.
410upload_expiredThe upload was not completed in time.
413quota_exceededIt would take the account past what it may store.
413app_quota_exceededIt would take the app past the ceiling set for it.
422idempotency_key_reusedThat Idempotency-Key was used for a different request.
422not_an_imageThe file is not an image that can be resized.
429rate_limitedMore than 600 calls from one key in a minute. Retry-After says how long to wait.