# Dropbox changelog

What changed in each Dropbox simulator release, newest first.

What changed in each Dropbox simulator release, newest first.

## v0.0.5 — 2026-11-05

1. **The webhook notifies the member on a mount, an unmount or a change of access level.** `mount_folder`, `unmount_folder`, and the owner's `update_folder_member` that changes a mounted member's access level each POST one notification naming that member alone, as Dropbox does. `share_folder` of an existing folder no longer notifies its owner, since Dropbox sends nothing for it.

2. **Moving the clock past a webhook retry sends it at once.** A failed notification still waits its 10 s to 320 s backoff on the wall clock, and `POST /_rystic/clock` past a retry's due now sends it straight away; a retry waiting out its delay no longer refuses a new notification as `queue_full`. The `delivery` report on `GET /_rystic/egress` adds `retries_armed`, `next_retry_at` and `next_retry_clock`.

3. **A request with no access token is refused on every route as Dropbox refuses it.** Before it reads the arguments the twin answers 400 `text/plain`, `Error in call to API function "<route>": Invalid authorization value in HTTP header/URL parameter`, or on most content routes that line in a JSON envelope tagged `other`, where it answered 401 `invalid_access_token`. An empty `Bearer` or another scheme gets the same 400, apart from `Basic`, which Dropbox reads as app authentication.

4. **A state dump seeds back as it was, and the next record gets a fresh id.** `GET /_rystic/inspect` posted to `POST /_rystic/seed` after a reset restores every record where it was, per-caller records included, which used to land under a doubled key. The next record created after any seed passes every uuid and event id the simulator minted that the seed holds. `GET /_rystic/inspect?view=sequences` lists every id counter, and a world document's new `sequences` key restores them, so ids the simulator renders from a counter continue where the dump left off.

5. **A slow client no longer holds up the others.** The simulator reads a request's whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients' requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.

6. **The call log shows what each call asked for and whether it succeeded.** Each `GET /_rystic/calls` entry now carries `args`, the call's query and body arguments with credentials redacted (long values cut, file contents left out), and `ok`, false for an answer that is not 2xx. A batch upload answered at HTTP 200 whose first entry failed is not ok either. A path the simulator does not serve is marked `unknown_route: true`. `?tail=N` returns only the last N calls, and `dropped` counts the calls the log no longer keeps.

7. **The docs are one page, with a page per feature.** The archive ships `README.md` in place of `HOWTO.md` and `SCENARIOS.md`, with the same sections in the same order as every Rystic simulator's page, and `CHANGELOG.md` beside it. Upload sessions and batches, listings and the change feed, deleted files and revisions, shared folders and file members, shared links and file requests, and tags each have their own page under `docs/features/`, and every example on these pages runs as written against a fresh simulator.

8. **The docs page links the admin token section.** The page now sends you to the admin token's section of the shared-simulators page.

9. **Scenario descriptions use customer terms.** The Scenarios table and `--list-scenarios` describe each scenario in American spelling, with no internal references.

10. **The Scenarios table is easier to scan.** Each row says in a line what the scenario is for, and a list below the table says what each scenario holds.

11. **Pro starts with a 14-day trial.** Free has ended, so every simulator is on Pro and Enterprise, and the docs name only those plans.

12. **Limitations is shorter to read.** It opens with the known issues and one sentence counting the rest. The detailed lists follow, and the website folds them behind "Show all limitations".

13. **The webhooks section names every answer of the URL route.** It lists what the `POST` refuses with 400, the 200 a set URL returns, the fields `GET` returns and the 204 `DELETE` returns.

14. **The page has no Plans section.** The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.

15. **The docs read more plainly, mark each difference from Dropbox where it's described and keep only the simulator's own facts.** Sentences are shorter, and each behavior that differs from Dropbox, such as the long poll's missing jitter or the 150 MiB upload-session cap, carries a "Differs from Dropbox" line beside it. How each route takes its argument, what a write does over an existing path, which webhook notifications each request sends and what an upload session does with a wrong offset are tables. `files/get_preview` is listed with the download routes, which return their result in the `Dropbox-API-Result` header and also take the `arg` query parameter. Seeding, sign-in and reset keep only what is particular to this simulator, such as its registers and their keys, and link the shared pages for the rest.

## v0.0.4 — 2026-10-15

1. **Swagger UI to read and call the twin from the browser.** `/_rystic/swagger`, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin.

## v0.0.3 — 2026-10-15

1. **Uploads up to 150 MiB per request.** `files/upload`, `upload_session/start` and `upload_session/append_v2` take bodies up to 150 MiB, the limit Dropbox documents per request, where anything over 1 MiB answered 413 or, mid-session, 409 `too_large`; the SDKs' 4 MiB chunked uploads now run end to end. A file, uploaded whole or through a session, holds at most 150 MiB on the twin.

2. **Batch sharing reads answer per-file errors, and a malformed cursor answers 409.** `sharing/get_file_metadata/batch` and `sharing/list_file_members/batch` answer 200 with an `access_error` (`invalid_file` for a path naming nothing, `is_folder` for a folder) on the entry it concerns, instead of failing the whole call with 500, and order a mixed answer as Dropbox does: errors first for file metadata, member lists first for members. A cursor that is not a cursor at all, such as `"bogus"`, answers 409 `invalid_cursor` on `file_requests/list/continue` and 409 `reset` on `sharing/list_shared_links`, which ignored it; a well-formed cursor that names no position still answers 500 text/plain, as does any bad cursor on `sharing/list_received_files/continue`, as Dropbox does.

3. **`add_folder_member` with no `access_level` adds a viewer.** An entry that omits `access_level` now joins the shared folder as a viewer, Dropbox's default, instead of an editor.

4. **A finished member removal warns in Dropbox's words.** `sharing/check_remove_member_job_status` answers `complete` with the `warning` text Dropbox sends.

5. **Seeded folders carry folder fields only.** Folders and deleted folders in the shipped worlds no longer carry `rev`, `size`, `is_downloadable`, `content_hash`, `client_modified` or `server_modified`, fields Dropbox sends on files only.

6. **An upload session's finish writes as `files/upload` does.** `files/upload_session/finish` of the bytes a file already holds answers that file unchanged, keeping its `rev`, where it answered 409 under mode `add` and minted a new `rev` under `overwrite`; `strict_conflict` and a stale `update` rev still answer `path/conflict/file`. A `commit.path` beneath a file answers `path/conflict/file_ancestor`, a folder there `path/conflict/folder` in every mode, a `commit.path` ending in `/` `path/malformed_path`, and a mode `WriteMode` does not name 400, where each was stored or overwritten.

7. **Batch moves and copies refuse what a single move or copy refuses.** An entry of `files/move_batch_v2` or `files/copy_batch_v2` whose `to_path` is `/`, ends in `/`, or lies beneath a file is that entry's failure in the job's result — `relocation_error` `to/malformed_path` or `to/conflict/file_ancestor` — and nothing is written for it, where the batch moved or copied it there; and a batch-moved file's `rev` comes from the same sequence as every other write's.

8. **`create_folder_v2` refuses a path ending in `/`, and three sharing errors read as Dropbox's.** `files/create_folder_v2` of `/` or of a path ending in `/` answers 409 `path/malformed_path` and creates nothing, where it created the folder without the slash or answered `conflict/folder`. `sharing/mount_folder`'s `already_mounted` and `sharing/unmount_folder`'s `access_error/unmounted` carry Dropbox's `user_message`, and `sharing/list_file_members/batch` answers an unknown file ID's 500 as plain text, not a JSON string.

9. **A write into a missing folder creates the folder.** An upload, an upload session's finish, a copy, a move, their batch forms and a `save_url` job into a path whose parent folders do not exist now create each missing parent, as Dropbox does, so `files/get_metadata` reads them as folders and `files/list_folder` lists them; you no longer need `create_folder_v2` first.

10. **`Dropbox-API-Path-Root` is checked against the caller's account.** If the root or namespace the header names is not the account's root or home namespace, the request now answers 422 `invalid_root` with the account's `root_info`. A value that is not a JSON root selector answers 400. Before this change both were ignored. `home`, the account's root namespace and its home namespace still answer from the account's own files.

11. **A shared folder is one folder in every member's Dropbox.** A member who mounts a share lists the owner's files, and an upload, move, copy or delete by any member reaches the owner and every other member. `files/get_metadata`, `list_folder` and search report `sharing_info` on a shared folder and everything inside it, with `read_only` true for a viewer, and `share_folder` on an already shared folder answers `bad_path/already_shared`. In `dropbox_team_shared` and `dropbox_paired_identity` the seeded shares resolve on `get_folder_metadata` and `list_folders` for every member, and both Acme members list `/Acme Inc Team Folder`.

12. **File members belong to the file, not its path.** Two accounts that each hold a file at the same path keep their own file members and sharing settings, and a file keeps its members through a move or rename.

13. **`list_folder/continue` is Dropbox's change feed, and `files/list_folder/longpoll` is served.** After a `list_folder` listing, or from a `get_latest_cursor` cursor, `continue` answers each path written below the cursor's folder since the cursor, once, in its final state: `deleted` for a deleted or moved-away path, and the entry for a created, copied, moved-in or overwritten one, the deleted and folder entries first and then the files. Before, a cursor was a position in the current listing, so a delete, a move, an overwrite and a name that sorts earlier never reached it. A cursor is never used up, its `limit` carries to every page, and sharing a folder under it lists the folder again. `files/list_folder/longpoll` takes no `Authorization` header, as on `notify.dropboxapi.com`. It answers `{"changes": true}` once a write lands in the namespace of the cursor's folder, or at once if one already has, and `{"changes": false}` after `timeout` seconds.

14. **The twin sends Dropbox's webhook.** Boot it with `RYSTIC_WEBHOOK_DELIVERY=on` and `POST /_rystic/webhooks {"url", "secret"}`: once your endpoint echoes the `GET ?challenge=` handshake, every write to a file or folder POSTs `{"delta":{"users":[<uid>]},"list_folder":{"accounts":["<account_id>"]}}`, naming the writer, or every account that has the shared folder mounted, signed `X-Dropbox-Signature` with your secret. Reads send nothing, and a failed notification is retried on Dropbox's documented backoff. Each seeded account carries a synthetic `uid`. Mounting, unmounting and changing a member's access send nothing yet, where Dropbox names the member.

## v0.0.2 — 2026-10-13

1. **`files/list_folder/continue` renders entries as the first page does.** A folder on a continuation page carries the folder fields only, never a file's `rev`, `size`, `content_hash` or timestamps, and a folder carries its `property_groups` on both pages, as Dropbox does.

2. **`users/get_space_usage` reports the account's files.** `used` is the total size of the account's files, rising with an upload and falling with a delete, and the allocation is an individual 2,147,483,648 bytes, the Basic plan's, where it answered 0 and 0.

3. **The shipped worlds' accounts and files read like Dropbox's.** Every world account ID is Dropbox's 40-character `dbid:` form, so `users/get_account` and `get_account_batch` find a world's own account, and every seeded file downloads `size` bytes whose block digest is its `content_hash` and restores from its `rev`. The large seeded files are smaller than before: a world now carries the bytes it declares.

4. **Void results are `null`, and unset optional fields are left out.** `sharing/revoke_shared_link`, `files/upload_session/append_v2` and `sharing/unshare_file` answer the JSON literal `null` instead of an empty body, and `files/search/continue_v2` and `file_requests/update` leave out an optional field the result does not hold instead of rendering it as `null`, so a strict JSON client reads them as Dropbox renders them.

5. **Listing a deleted folder answers `path/not_found`.** `files/list_folder` on the path of a folder you deleted answers 409 `path/not_found`, as Dropbox does; `path/not_folder` now means the path is a file.

6. **A shared link serves its file's bytes.** `sharing/get_shared_link_file` answers the file's bytes, a file you uploaded included, where it answered an empty body.

7. **Member changes take effect.** `add_folder_member` and `add_file_member` by email add the account that holds the address, or list an invitee when none does; a member demoted to viewer gets `no_write_permission` on upload, create and delete inside the share and reads it `read_only: true`; `remove_folder_member`, `relinquish_folder_membership` and `unshare_folder` take the folder out of the member's Dropbox, and an unshared folder can be shared again; a file's members follow it through a move.

8. **Deleting a copied folder deletes its contents.** `files/delete_v2` of a folder made by `files/copy_v2` answers it as a folder and removes the copied files with it; each copied file has its own `rev`, and `copy_v2` answers `is_downloadable`.

9. **Shared links follow their file.** A second `create_shared_link_with_settings` answers `shared_link_already_exists` carrying the existing link's metadata; a link follows its file through a move and goes with it on delete; a password without `link_password`, or an expiry in the past, answers `settings_error/invalid_settings` on a Pro account; and `get_shared_link_file` on a folder link answers `shared_link_is_directory`.

10. **Downloads take `?arg=` and `Range`.** The download routes read their argument from the `arg` query parameter when the `Dropbox-API-Arg` header is absent; `files/download` and `sharing/get_shared_link_file` answer a byte `Range` with 206 and `Content-Range`.

11. **Batch moves and deletes act as their one-entry routes.** A folder moved by `files/move_batch_v2` takes its contents with it, and a moved file keeps its members and shared links at its new path; `files/delete_batch` leaves a tombstone, deletes a folder's contents with it and removes the deleted file's shared links, as `files/move_v2` and `files/delete_v2` do.

12. **Batch copies carry their bytes and their folder's contents.** A file copied by `files/copy_batch_v2` downloads its source's bytes under a `rev` of its own, and a folder it copies arrives with everything beneath it, as with `files/copy_v2`.

13. **Six sharing errors carry Dropbox's `user_message`.** `add_file_member` and `remove_file_member_2` on an invalid file, `relinquish_folder_membership` on an invalid share, and `mount_folder`, `relinquish_folder_membership` and `update_folder_policy` by a non-member answer `user_message {locale, text}` beside the error, as Dropbox does.

14. **A deleted entry reads as `DeletedMetadata`, and downloads as not found.** `files/get_metadata` with `include_deleted: true` answers a deleted entry with only `.tag`, `name`, `path_lower` and `path_display`, as Dropbox does, and `files/download` of a deleted path answers 409 `path/not_found` instead of `path/not_file`.

15. **A deleted file lists its revisions and restores.** `files/list_revisions` on a file you deleted answers its revisions with `is_deleted: true` and `server_deleted`, instead of 409 `path/not_file`, so a recover-a-deleted-file flow can pick a `rev` and pass it to `files/restore`; `mode` also takes the bare tag (`"id"`). The `dropbox_deleted_backlog` world's four deleted files now list and restore too.

16. **A rename keeps a file's revision history.** `files/move_v2` and `files/move_batch_v2` give a moved file a new `rev`, as Dropbox does, and `files/list_revisions` in `id` mode lists the file's revisions from before and after the rename.

17. **Search renders a folder as a folder.** `files/search_v2` and `files/search/continue_v2` answer a folder without a file's `rev`, `size`, `content_hash` or timestamps, and neither kind with `is_restorable`, as `files/list_folder` renders them.

18. **Three more sharing errors carry Dropbox's `user_message`.** `add_folder_member` with a malformed `dropbox_id`, `remove_file_member_2` of a file's owner and `update_folder_policy` on an unknown share answer `user_message {locale, text}` beside the error, as Dropbox does.

## v0.0.1 — 2026-10-13

1. **A local Dropbox for your integration and the agents that drive it.** The first release of the Dropbox twin: 88 operations of the Dropbox API v2 — one account's files and folders, listings and search with their cursors, shared links, file requests, shared folders and file members, upload sessions, batch jobs and `save_url`, and tags — served on one port for both `api.dropboxapi.com` and `content.dropboxapi.com`, from a model validated against a live Dropbox account. Point the official JavaScript SDK at it through a `fetch` that rewrites the two hosts, or curl it with any bearer token (`HOWTO.md` shows both); nothing reaches Dropbox and no real account changes.

2. **Files and folders, created, uploaded and read back.** `files/create_folder_v2`, `get_metadata`, `list_folder`, `move_v2`, `copy_v2`, `delete_v2`, `get_temporary_link`, `list_revisions` and `restore` take a JSON body; `files/upload` and `download` take their argument in the `Dropbox-API-Arg` header beside the bytes, as on the content host, and `download` answers the file's metadata in `Dropbox-API-Result`. A path that holds nothing answers 409 `path/not_found`, a create or an add-mode upload over an existing entry 409 `path/conflict`, and `autorename` lands the upload as `name (1)`. A malformed path answers as Dropbox does: one that fails a route's path pattern answers the 400 plain-text input error, and one ending in a trailing slash answers 409 `malformed_path`, on `download`, `delete_v2` and `get_metadata`. Every upload, session finish and restore records its own `rev`. Every folder `create_folder_v2` creates has its own `id`, and each account holds its own files: a second account reading the first one's unshared folder gets `path/not_found`, while a file shared with it reaches it through `sharing/list_received_files` and `get_file_metadata`.

3. **Listings and search page by cursor, as Dropbox's do.** The cursor `files/list_folder` answers resumes the listing through `list_folder/continue`, for a folder addressed by path or by `id:`, and `get_latest_cursor` hands out the cursor for a folder's current state, whatever property templates `include_property_groups` names; a `limit` outside 1 to 2000 answers the 400 input error, and the cursor of a deleted folder answers 409 `path/not_found`. `files/search_v2` matches the words of a name, case-insensitively, scoped by `options.path`; a `max_results` smaller than the matches answers a standard-base64 cursor that `search/continue_v2` walks, and a cursor the twin never handed out answers 400.

4. **Shared links, file requests, shared folders and file members.** `sharing/create_shared_link_with_settings` mints a `www.dropbox.com` link, refusing a password on a Basic account with 409 `settings_error/not_authorized` as Dropbox does, that `list_shared_links`, `get_shared_link_metadata` and `modify_shared_link_settings` read and change, `get_shared_link_file` downloads through, and `revoke_shared_link` ends. `file_requests/create` opens a request at a destination folder; a `list/continue` cursor that is not a live file-requests listing position answers 500 text/plain, as Dropbox does; `delete` refuses an open one with 409 `file_request_open` until `update` closes it, and `count`, `list`, `list_v2` and `delete_all_closed` cover the account's requests. `sharing/add_file_member` gives an account access to a file, which `list_file_members` lists and `get_file_metadata` reports; the account that calls `share_folder` is the share's owner, so `list_folders` and `list_mountable_folders` list it, `get_folder_metadata` and `list_folder_members` answer it with `access_type` owner and `unshare_folder` launches its job; `add_folder_member`, `mount_folder` and the folder-member routes are served, and a mounted share lands in the member's own Dropbox at the `path_lower` the mount answered. No email is sent for any of them.

5. **Large files in pieces, folders as a zip, and batch jobs that complete on the second check.** `files/upload_session/start`, `append_v2` and `finish` commit a file's bytes in order, and an append whose `cursor.offset` differs from the bytes held answers 409 `incorrect_offset` with `correct_offset`. A second `finish` of a finished session at the held offset commits its bytes again at the new `commit.path`, at any other offset it answers 409 `lookup_failed/incorrect_offset`, and an `append_v2` after finish answers 409 `closed`; a session ID the twin never minted answers 500 text/plain, as Dropbox does, and another account's 409 `lookup_failed/not_found`. `start_batch`, `append_batch` and `finish_batch_v2` do the same for many sessions. `download_zip` answers a folder as `application/zip` with its metadata in `Dropbox-API-Result`. `save_url`, `copy_batch_v2`, `move_batch_v2` and `delete_batch` answer an `async_job_id` whose check answers `in_progress` first and `complete` on the next check, or once the clock has moved two seconds past the launch, as measured on Dropbox; `create_folder_batch` answers `complete` inline. Once a `save_url` job answers `complete`, `get_metadata` and `list_folder` show its file at the job's path and `download` answers its bytes, a placeholder derived from the URL, since `save_url` fetches nothing.

6. **Tags on a file or folder.** `files/tags/add`, `tags/get` and `tags/remove` add, read and remove a file's or a folder's tags; `tags/get` answers each path's tags as `user_generated_tag` entries, removing a tag the entry does not carry answers 409 `tag_not_present`, and a `tag_text` outside lower-case letters, digits and underscores answers 500, as Dropbox does.

7. **Five shipped Dropboxes.** `--scenario dropbox_personal_archive` boots Ada Sealy's Pro account and a nine-entry tree with revs, hashes and sizes, the world `check.sh` and the quickstart use. `dropbox_fresh_signup` holds an unverified Basic account and only `/Apps`, `dropbox_deleted_backlog` five deleted entries listed as tombstones beside one surviving file, `dropbox_paired_identity` a personal account paired with a Northwind work account, each with its own tree, and `dropbox_team_shared` two Acme members with their own tokens (`sl.rosa-desktop`, `sl.toni-laptop`), a shared team folder and a timeline the clock plays: Toni creates `/Drafts`, uploads a sketch and shares it with Rosa, then Rosa files her scratch note and deletes `/Scratch`. `SCENARIOS.md` says what each is for. `POST /_rystic/seed` lands accounts, entries, revisions, links, requests and shares directly, and `POST /_rystic/reset` rewinds the sequences and the clock, so two identical runs give byte-identical entries.

8. **Measured against a live Dropbox account.** The full-suite round of 2026-10-12 (`20261012T232831Z`) matched 214 of 246 probes against a live Dropbox account, with 19 inconclusive: the sharing-member probes Dropbox's daily invite cap leaves rate-limited, which the round scores inconclusive rather than failed. `HOWTO.md` states the differences this release ships with: a missing `Authorization` header and Dropbox's text/plain error bodies are answered as JSON, the twin's 500 answers are not compared against Dropbox, `files/tags/get` by a folder's `id` is unmeasured, and search matches names only, never a file's content.

---

Source: https://www.rystic.ai/docs/simulators/dropbox/changelog · Markdown: https://www.rystic.ai/docs/simulators/dropbox/changelog.md
