Troubleshooting
Questions that come up
Section titled “Questions that come up”Every call answers 404 with {"error":"not_found"}
Section titled “Every call answers 404 with {"error":"not_found"}”That isn’t the API’s own 404 (which is a problem document). It’s what every address under /api/v1 gives on a site where Allow connected apps is switched off (or the plan lacks it), on purpose, so the API looks as though it isn’t there. Ask the Foldr administrator to turn it on in Settings > Connected apps. The OAuth endpoints answer the same way.
401 invalid-token, and I just got the token
Section titled “401 invalid-token, and I just got the token”Every token failure is the same answer, with no reason, so you can’t tell which of these it is:
- the token has expired (they last an hour): refresh it;
- it’s been revoked, or the person disconnected the app;
- the person has been disabled or removed, or has lost the right to connect apps (their tokens are refused while the right is missing);
- it’s a token from another site: each site issues its own;
- it was bound to a different resource when you asked for it (a token tied to another API doesn’t work under
/api/v1); - the header isn’t
Authorization: Bearer <token>, or the token was cut short by a copy and paste.
Refresh once and retry. If the refresh fails with invalid_grant, send the person through sign-in again.
403 insufficient-scope or 403 write-not-allowed?
Section titled “403 insufficient-scope or 403 write-not-allowed?”insufficient-scope: the token doesn’t carry the scope this call needs (WWW-Authenticate names it). Check the scope in your token response. Either you didn’t ask for it, or the app’s policy didn’t give it, so it was dropped at sign-in.
write-not-allowed: the token has the scope, but this change isn’t allowed here: the person can’t make it in Foldr, or the organisation hasn’t allowed this app to make this kind of change, or the person may only read the approved version of a published file. Retrying doesn’t help; tell your user.
A share I know exists answers 404
Section titled “A share I know exists answers 404”404 is deliberately the same for: no such share; a share that isn’t this person’s; a path that the share’s rules hide; and a share your app’s policy doesn’t name (for changes). Check you’re calling as the person you think you are (getMe), and that the share is in listShares. A share that opens only with the person’s own password (some SMB and WebDAV setups) isn’t visible to apps, and so listShares can be shorter than what the person sees in Foldr’s web app.
listShares shows a share, but opening it gives 409 storage-account-not-connected or 503
Section titled “listShares shows a share, but opening it gives 409 storage-account-not-connected or 503”storage-account-not-connected: it’s in the person’s own cloud account (Google Drive, OneDrive, Dropbox, Box) and they haven’t yet given Foldr their sign-in to that service. They do that once in Foldr’s web app, by opening the share there or under Me › Services; your app can’t do it for them. 503 share-unreachable or share-unavailable: its file server isn’t answering just now; try again after Retry-After. Such shares are listed with reachable: false.
428 precondition-required, then 412 precondition-failed
Section titled “428 precondition-required, then 412 precondition-failed”428: you left out If-Match (or If-None-Match: * on an upload). 412: you sent it, and the item has changed since you read it. Read it again, and decide again. Don’t loop on 412 by re-reading and re-sending the same change blindly: someone else changed the thing, and that may matter to your user. See ETags.
If-Match: * is refused
Section titled “If-Match: * is refused”By design, for overwrite, move, delete and version restore: name the version you mean. renameItem and setFieldValue accept *, meaning “whatever the item is now”; send a specific tag anyway, so that you catch a change made since you read it.
A retry answers 409 conflict
Section titled “A retry answers 409 conflict”Either the name is taken, or the same Idempotency-Key is still being handled. The detail and timing tell which: if your first request is still running (a big upload, say), wait, then send the same request with the same key. A request that was cut off frees its key about two minutes after it last made progress.
A retry answers 422 idempotency-key-reused
Section titled “A retry answers 422 idempotency-key-reused”You sent the same key with a different request. Keys are for “send the same thing again”. Make a new key for each new request.
400 invalid-request and I can’t see why
Section titled “400 invalid-request and I can’t see why”Look at detail. The usual causes: a query parameter given twice (?path= twice, or a list); a limit of 0 or not a number; JSON that doesn’t parse or is over 1 MB; no Idempotency-Key where one is required; an upload without Content-Length; If-Match on uploadFile; If-Match: * where a version must be named; a token request that isn’t form-encoded.
The path has #, &, % or accents and it doesn’t work
Section titled “The path has #, &, % or accents and it doesn’t work”Percent-encode the value of path with your HTTP library’s own query encoder, and don’t build the query string by hand. Either Unicode form is accepted for accents. See Paths.
A download redirects somewhere and then fails
Section titled “A download redirects somewhere and then fails”On shares with direct downloads, getContent answers 302 to the storage. Follow it without your Foldr token. A client that sends the bearer token to the storage host gets an error back from the storage. If your tool can’t follow a redirect to another host, use getDownloadUrl.
A Google Doc downloads as .docx with no length
Section titled “A Google Doc downloads as .docx with no length”That’s right: a native Google document is exported when read, so it has no fixed size, no Content-Length and no ranges. See Reading.
A folder listing is missing things, or shows things that were deleted
Section titled “A folder listing is missing things, or shows things that were deleted”A listing is a snapshot kept for a minute, so changes made after the first page don’t show in later pages. For the truth about one item, call getItem. And check truncated: a folder of more than 25,000 entries is paged as its first 25,000, and says so on every page.
Search returns nothing, or less than I expected
Section titled “Search returns nothing, or less than I expected”Check not_searched in the answer: shares that aren’t indexed, can’t be reached, or whose search index didn’t answer are named there with a reason. Search covers indexed shares only, and only what the person may see.
A copy or move came back 202 and I don’t know what to do
Section titled “A copy or move came back 202 and I don’t know what to do”Poll the Location until it’s no longer 202. See Operations. If you’ve lost the id, listOperations finds it for 7 days after it ends.
An operation is 404
Section titled “An operation is 404”It’s more than 7 days since it finished, or it belongs to another person or another app, or your token has one of the operation scopes but not the one the operation was started under. These all look the same.
My change has been 202 for days
Section titled “My change has been 202 for days”It’s held for approval (pending_approval). Someone other than the person has to approve it in Foldr, and nobody has yet. It lapses after 14 days (unless the organisation set another period), when it ends change-expired. Poll slowly, at the Retry-After it gives. See Changes held for approval.
I’m told I have to connect again
Section titled “I’m told I have to connect again”The connection ended: the person or an administrator disconnected the app, the refresh token’s lifetime or idle limit was reached, a used refresh token was presented again (which revokes the whole chain, so refresh from one place at a time), or the person left. Start the sign-in flow again.
429 and my retry made it worse
Section titled “429 and my retry made it worse”Wait for Retry-After, don’t retry everything at once afterwards, and back off further on repeated 429s. Look at X-RateLimit-Remaining to slow down before you’re stopped. daily-write-limit and destructive-budget run to midnight UTC: don’t sleep through them, tell the user. See Rate limits.
OPTIONS is refused and a browser can’t call the API
Section titled “OPTIONS is refused and a browser can’t call the API”The API sends no CORS headers and answers OPTIONS with 405. It’s meant to be called from a server, a script or an automation platform. A web app should call it from its own back end.
Who do I tell about a problem?
Section titled “Who do I tell about a problem?”Quote the instance code in the problem document (# and 8 hexadecimal digits) when you ask support, along with the time, the operation (operationId) and the OpenAPI document’s info.version.
A checklist for a well-behaved client
Section titled “A checklist for a well-behaved client”Signing in
- Uses the authorisation code flow with PKCE (
S256) and a randomstate, and checksstateandisson the way back. - Sends
resourceset to the site’s/api/v1. - Asks only for the scopes it needs, plus
offline_accessif it should stay connected. - Reads the
scopein the token response, and copes with getting less than it asked for. - Keeps tokens in a safe place, never in logs, URLs or source control.
- Saves each new refresh token before it uses the new access token, and refreshes from one place at a time.
- Refreshes on
401 invalid-tokenonce, and sends the person back to sign in oninvalid_grant. - Revokes its tokens (
/oauth/revoke) when the person disconnects the app.
Calling
- Ignores fields it doesn’t recognise, and accepts enum values it doesn’t know (a share
typeit hasn’t seen isother). - Branches on a problem’s
typeorcode, never ontitleordetail. - Keeps the canonical
pathfrom each answer and uses it for what follows. - Percent-encodes
pathwith a proper encoder, and never gives a parameter twice. - Treats cursors as opaque, and handles
410 cursor-expiredby starting over. - Checks
truncatedandnot_searched. - Honours
Retry-Afteron every429and503, spaces out retries, and readsX-RateLimit-Remaining. - Uses
If-None-Matchfor repeat reads to get cheap304s.
Changing things
- Sends
If-Matchwith the ETag it last read, and on412reads again instead of forcing. - Sends a new
Idempotency-Keyfor each new request, and the same key when it repeats one. - Reads the
pathin a create’s answer:on_conflict: "rename"may have chosen another name. - Sends
Content-LengthandIf-None-Match: *on uploads, and at most a few uploads at a time. - Treats
202as “started”, followsLocation, and handles every way an operation can end, includingsource-remains. - Reads the item again after
503 write-failedbefore assuming the change did or didn’t happen. - Tells its user, in words, about
legal-hold,checked-out,published,not-recoverable-here,change-declinedand the daily limits, and doesn’t retry them. - After a delete, tells the user from
recoverableandwherewhether it can be brought back. - Never presents a share request as sharing: it’s a request, and an approver decides.
Staying stable
- Uses
operationIds and the OpenAPI document, not hand-copied URLs, when it can. - Watches for
DeprecationandSunsetheaders.