Skip to Content
SimulatorsGoogle Drive changelog

Simulators

Google Drive changelog

What changed in each Google Drive simulator release, newest first.

v0.0.8 — 2026-11-05

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

  2. Shared drives, download operations and batch requests. drives.create, drives.get, drives.list, drives.update, drives.delete, drives.hide and drives.unhide serve shared drives: drives.create needs a requestId and answers 409 teamDriveAlreadyExists to one the same user already used on a drive that still exists, a drive’s members hold organizer, fileOrganizer, writer, commenter or reader, drives.get, drives.update, drives.hide and drives.unhide answer 404 to a caller who is not a member, only an organizer renames or deletes a drive, and a drive that still holds an untrashed item answers 403 cannotDeleteResourceWithChildren. files.create places a file in a drive, and a permission on a drive’s id adds a member. POST /drive/v3/files/{fileId}/download answers an operation that is already done, whose downloadUri fetches the bytes, and GET /drive/v3/operations/{name} reads it back for the user who started it. POST /batch/drive/v3 runs each part of a multipart/mixed body as its own request and answers one response part per part, in order. files.list with includeItemsFromAllDrives=true and changes.getStartPageToken with driveId refuse a missing supportsAllDrives as Drive does, changes.list with driveId also refuses a missing includeItemsFromAllDrives, and the change routes with driveId answer 404 for an id that names no drive and 403 teamDriveMembershipRequired to a caller who is not a member.

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

  4. Coverage lists resumable uploads. The docs’ Coverage section now lists the resumable upload session routes under /upload/drive/v3/files and /upload/drive/v3/files/{fileId}, which the simulator already served, and counts them in its route total.

  5. The docs are one page, with a page per feature. The archive ships README.md in place of HOWTO.md, as the standard simulator page, and CHANGELOG.md beside it. The change feed and shared drives each have their own page under docs/features/, as do uploads and downloads, and every example on these pages runs as written against a fresh simulator.

  6. The docs page links the admin token section. The page now sends you to the admin token’s section of the shared-simulators page.

  7. Domain-admin access and shared-drive roles. useDomainAdminAccess=true is served on drives.get, drives.list, drives.update and drives.delete and on permissions.list, permissions.create, permissions.get, permissions.update and permissions.delete for a shared drive: it opens the drive and its member list to a domain administrator who is not a member, and never the drive’s contents, so the administrator’s files.create or upload into the drive answers 404 notFound and stores nothing. A caller who is not an administrator is refused or answered 404, as Drive does, and allowItemDeletion=true with the flag deletes a drive that still holds items. A seeded user marked domainAdmin is an administrator, and a new drive is visible to it at once, where Drive can take up to about 10 seconds. Any member of a shared drive can read one member’s permission with permissions.get on the drive’s id. A multipart upload into a shared drive, or into a folder inside one, now stores the file as one of the drive’s items, where the simulator answered 500 or left it outside the drive. In a shared drive, trash, move and share follow the caller’s role: a reader and a commenter are refused with 403 insufficientFilePermissions, a writer can share but neither move nor trash, and a fileOrganizer or organizer can do all three. As on Drive, files.copy without supportsAllDrives=true answers 404 on a file in a shared drive and files.list without includeItemsFromAllDrives=true leaves shared-drive files out. Right after a grant on a shared drive, Drive can refuse the new member’s comments.create with 403 insufficientFilePermissions for up to about two minutes, while the simulator accepts it at once, so retry that 403 with backoff.

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

v0.0.7 — 2026-10-31

  1. Seed large files without inlining their bytes. A seeded revision can name its bytes as a file under the folder RYSTIC_CONTENT_ROOT names, "content_ref": "file:<path>", in place of body_b64; downloads stream that file from disk without loading it into memory, byte ranges included, and a copy points at the same file instead of duplicating it. A seed naming a file that does not exist is refused.

v0.0.6 — 2026-10-27

  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.5 — 2026-10-14

  1. This version carries every change listed under v0.0.4. v0.0.4 was withdrawn minutes after it shipped, when a check of its downloads hit a transient error, so no customer kept it. This version ships the same twin, including the change feed (changes.getStartPageToken and changes.list).

v0.0.4 — 2026-10-14

  1. Resumable uploads. uploadType=resumable on POST /upload/drive/v3/files and on the PATCH of an existing file answers a Location session URI, takes the bytes in chunks under Content-Range (308 with a Range header until the last one) or in one PUT, answers a status query, and creates or updates the file when the last byte arrives, so a Google client that switches to a resumable upload for a large file works. A POST with no uploadType is a media upload, as on Drive, and an unknown uploadType answers 400. A multipart upload takes its mimeType from the metadata, else the media part, else the name’s extension, and stores a nameless file as Untitled.

  2. A bearer token must look like a Google access token. The twin now accepts a bearer value only when it starts ya29., the prefix every Google access token carries, and refuses any other as Drive refuses an invalid token: 401 authError with WWW-Authenticate: Bearer … error="invalid_token". A client that sent a made-up token such as test must send one that starts ya29., such as ya29.placeholder.

  3. More of Drive’s refusals, as Drive words them. A reader or commenter is refused a rename, an upload, a comment or reply edit, a reply or permission delete and a file in someone else’s folder; a non-owner is refused a delete; a copy or download of a copyRequiresWriterPermission file by a reader or commenter answers 403 cannotCopyFile or cannotDownloadFile; a read-only-locked file refuses a rename and a comment; size or another read-only member in an update answers 403 fieldNotWritable; a folder moved into itself or its own child answers 400; a plain file named as a parent answers 403 parentNotAFolder. addParents without removeParents moves the file, a mimeType in an update is ignored, and a download ignores fields, as on Drive. Each answer was measured on Drive.

  4. An upload over 64 MiB names the cap. A media or multipart upload over 64 MiB answers 413 request body exceeds blob_cap_bytes 67108864, the cap /_rystic/version reports, where it answered request body too large.

  5. A Google Doc’s revision reads and updates as on Drive. A Doc lists one revision, ID 1, with its ten exportLinks and no md5Checksum, size or keepForever. Updating it answers 200, and deleting it answers 403 revisionDeletionNotSupported. Before, the list named a revision no other call could find, and a read or update of it answered 404.

  6. A deleted reply is scrubbed, and a plain reply reopens a resolved comment. Reading a deleted reply with includeDeleted=true answers only kind, id, createdTime, modifiedTime, htmlContent "" and deleted, and its modifiedTime is when it was deleted. A reply without an action on a resolved comment carries action: "reopen" and reopens the comment, as on Drive.

  7. Docs and folders you create carry Drive’s links and size. A Google Doc made by files.create has size “1024”, its ten exportLinks and a webViewLink on docs.google.com/document/d/<id>/edit. A folder’s webViewLink is drive.google.com/drive/folders/<id>. The seeded Docs list all ten export formats.

  8. A change feed to poll. GET /drive/v3/changes/startPageToken and GET /drive/v3/changes serve Drive’s change feed: take a start token before listing with files.list, poll from it, read on with nextPageToken while a page carries one, and keep the last page’s newStartPageToken for the next poll (pageSize 1 to 1000). Each file created, updated, uploaded to, moved, trashed, shared or unshared since the token is listed once, holding its state as the caller reads it now, and one the caller can no longer read is listed removed: true; each user’s feed holds only what that user can read. The twin lists a change at once, where Drive lists it 1 to 4 s later, and POST /_rystic/reset restarts its tokens. changes.watch, files.watch and channels.stop are not served. The feed’s known gaps, among them fields, the shared-drive parameters and a folder’s trash, are listed with their tickets in the docs.

  9. The change feed lists what Drive lists. A file someone stops sharing with you, or deletes, is now listed removed: true in your feed even if you found it through files.list or files.get rather than the feed. A token’s answer no longer depends on which polls came before it: reading it again answers the same while nothing has changed since, and picks up later writes in their current state. A file you reach only through a link is never listed. Trashing or untrashing a folder lists every file below it. fields now narrows both changes routes: with no fields, each listed file holds only kind, id, name and mimeType; an empty fields answers {}; and a member a change lacks answers 400 invalidParameter. A new comment, a revision kept forever and a deleted revision now list their file, while a reply, even one that resolves its comment, lists nothing. The feed refuses driveId, includeItemsFromAllDrives and spaces as Drive does, restrictToMyDrive=true lists only the files you own, and a request with no credential names DriveChanges.List or DriveChanges.GetStartPageToken in its 401.

v0.0.3 — 2026-10-13

  1. Seeded files and copies download their bytes, and a file carries only the members Drive gives it. Every binary file in a shipped world downloads the bytes its size and md5Checksum describe (sizes are now tens of kilobytes), and a files.copy downloads its source’s bytes under its own head revision and createdTime. A file no longer answers "", {} or null for a member Drive leaves out, so the Go client decodes every world’s file list; a file made from metadata alone carries size: "0", md5Checksum, headRevisionId and its links, owners carry an email, and trashing a My Drive file sets no trashedTime.

  2. A files.list page walk survives deletes, and no listing answers the root. A pageToken keeps working after the files of an earlier page are deleted or trashed: the walk continues with the files still after it, as Drive’s does. Each world’s My Drive root folder is no longer a row of files.list; files.get on its ID still answers it.

  3. Ownership transfer works as Drive’s. An owner’s permissions.create with transferOwnership=true makes the grantee the owner at once and leaves the old owner a writer, so owners, ownedByMe and capabilities follow; a writer’s transfer answers 400 invalidSharingRequest “Sorry, you do not have permission to share.”

  4. A read-only content restriction locks a file’s content. A file whose contentRestrictions hold readOnly: true answers canModifyContent, canRename and canComment false for every caller (canEdit stays true, as on Drive), a media update of it answers 403 contentRestrictionViolation, and files.update stores the restriction with its type, restrictingUser and restrictionTime. The Security Policy doc in google-drive_team_workspace now agrees with its restriction.

  5. Folders behave as a tree. files.update with addParents and removeParents moves a file, and '<folderId>' in parents finds it under its new folder. A file inside a trashed folder reads trashed: true with explicitlyTrashed: false, drops out of q=trashed = false, and comes back when the folder is restored. The ID root names your My Drive root on every route that takes a file ID, and a file created without parents, by files.create or either upload type, lands in that folder under its real ID.

  6. files.list takes more of Drive’s search terms. q now takes fullText contains (whole words of the name, the description or a text file’s content), sharedWithMe, 'me' in readers and in writers (a role at or above it, inherited through folders), an email or permission ID in owners, createdTime, modifiedTime <, != on trashed and starred, appProperties has, double-quoted values, True/False in any case, and name contains words in any order. Without orderBy, or with an empty one, files come newest modifiedTime first, and an empty sort key is skipped. q=name < 'x' still answers 400, where Drive answers 500.

  7. An upload holds up to 64 MiB. A media or multipart upload, create or update, now stores and downloads byte for byte up to a 64 MiB request body, where it stopped at 1 MiB; a multipart body’s metadata and part headers count toward it. A larger body answers 413 request body too large, and /_rystic/version reports the cap as blob_cap_bytes.

  8. 'root' in parents lists My Drive’s top level. files.list reads root in q’s parents term as the caller’s My Drive root, as every route that takes a file ID already does, so q='root' in parents answers the files and folders directly under it.

  9. Every user has a My Drive root. root resolves for every user in every world, after POST /_rystic/reset too, and a file or folder created with no parents lands in it. A folder you create no longer becomes the root and drops out of files.list.

  10. A media upload onto a folder leaves it a folder. PATCH /upload/drive/v3/files/{folder}?uploadType=media answers 200 with the folder’s metadata, as Drive does. The twin writes nothing, where it turned the folder into a file; what Drive does with the uploaded bytes is unmeasured.

  11. JSON answers carry Drive’s charset. Every JSON answer is labeled Content-Type: application/json; charset=UTF-8, as Drive labels it, where it was bare application/json.

v0.0.2 — 2026-10-13

  1. A comment’s author carries a name and photo, and resolved appears only once a reply acts.

    • Every comment’s and reply’s author carries displayName and a protocol-relative photoLink (//lh3.googleusercontent.com/...) from the author’s user record, with me true only for the caller who wrote it.
    • The seeded users carry a displayName and a photoLink, so a seeded caller’s comments render a full author.
    • A comment answers no resolved until a reply with action resolve or reopen acts on it, then true or false; a deleted comment read with includeDeleted=true carries none.

    Before, an author whose user record lacked a name or photo answered without them, and every comment answered resolved: false.

  2. A request with no credential answers what Drive answers on that method.

    • Most methods answer 401 reason required, message “Login Required.”, status UNAUTHENTICATED, with one google.rpc.ErrorInfo detail: reason CREDENTIALS_MISSING, service drive.googleapis.com and the method’s own name, such as google.apps.drive.v3.DriveFiles.Create. Revision downloads (revisions.get with alt=media) are among them.
    • files.list, files.get and every comments and replies method answer 403 forbidden, status PERMISSION_DENIED, “Method doesn’t allow unregistered callers …”.
    • files.get with alt=media and files.export answer 403 forbidden “The request is missing a valid API key.” files.export checks its required mimeType first.
    • An empty bearer value or another scheme counts as no credential. The check runs before the file lookup, so an unknown ID still answers the credential refusal.

    Before, most methods answered 401 authError “Invalid Credentials”, and some carried a placeholder example.googleapis.com detail.

    A bearer that is present is still accepted whatever its value: the twin does not check token validity, where Drive answers a bogus token 401 authError “Invalid Credentials”.

  3. permissions.list orders entries as Drive does.

    anyone permissions come first, then domain permissions, then user permissions, where the owner’s entry leads the other user grants, which follow in the order they were made. With pageSize=1 on a file shared by link, the first page is the anyone permission and nextPageToken continues to the owner.

    Before, the owner’s entry always came first.

  4. revisions.list refuses an unknown pageToken, and two comment routes check the credential first.

    • revisions.list with a pageToken it never issued answers 400 reason invalid, message “Invalid Value”, at location pageToken, as Drive does. An out-of-range pageSize keeps its own 400 invalidParameter answer.
    • comments.update and replies.create with no credential answer Drive’s 403 before any other check.

    Before, an unknown pageToken answered the pageSize refusal, and those two routes answered 401 to a missing credential.

  5. A multipart upload’s base64 media part is stored as its decoded bytes. An uploadType=multipart upload whose media part carries Content-Transfer-Encoding: base64 now stores, sizes and checksums the decoded bytes, so alt=media answers the original content; before, the twin stored the base64 text. Base64 that does not decode answers 400 parseError, as any malformed multipart body does.

  6. A fields selector naming a member the resource lacks answers 400, and comments.list ignores an unknown pageToken.

    • A fields selector naming a member the method’s resource does not have, at any depth (nope, id,nope, owners(nope), capabilities/nope, ID), answers 400 invalidParameter “Invalid field selection nope” at location fields, as Drive does. A create refused this way still writes the file.
    • comments.list with a pageToken it never issued answers 200 with the first page, as Drive does.

    Before, such a selector answered 2xx with nothing for that member, and comments.list refused the token with 400 invalidParameter.

  7. files.update accepts a member of the wrong type, and an upload refused for its fields writes nothing.

    • files.update with a member of the wrong type answers 200 as Drive does:
      • a non-string name, such as {…}, leaves the name unchanged;
      • a non-boolean trashed, such as "x", reads as false and restores a trashed file;
      • any other wrong-typed member is ignored.
    • A body that is not valid JSON still answers 400 parseError.
    • A media or multipart upload, create or update, whose fields names a member the File resource lacks answers 400 and writes nothing: no file is created, and an updated file keeps its bytes, as Drive does.

    Before, the twin answered files.update 400 invalidParameter for a wrong-typed member, and wrote the refused upload.

v0.0.1 — 2026-10-13

  1. A local Google Drive for your integration and the agents that drive it. The first release of the Google Drive twin: 35 operations of the Drive API v3 — one user’s files and folders, uploads and downloads, files.list with q, orderBy and page tokens, revisions, exports, permissions, and comments and replies — served on one port for www.googleapis.com’s /drive/v3 and /upload/drive/v3 routes, from a model validated against live Google Drive. Point Google’s Node or Python client at it, or curl it with any bearer token (HOWTO.md shows how each client’s uploads reach the twin, since an upload left unpointed goes to the real Google Drive); nothing reaches Google and no real Drive changes.

  2. Files and folders, created, read, trashed and deleted. POST /drive/v3/files creates a file, or a folder with mimeType application/vnd.google-apps.folder, under the folders its parents name; files.get, files.update, files.copy and files.delete read, change, copy and delete it, and every file a shipped world seeds reads back. PATCH with trashed moves a file to the bin and back, and DELETE /drive/v3/files/trash empties the bin. A create naming an ID files.generateIds issued keeps it, an ID a file already holds answers 409 fileIdInUse and changes nothing, and an ID generateIds never issued answers 400 fileIdNotUsable. A missing or deleted file answers 404 notFound in Drive’s JSON error envelope.

  3. Uploads, downloads, revisions and exports. uploadType=media and uploadType=multipart create and update a file’s bytes on /upload/drive/v3, as the official clients send them, and alt=media reads them back: a Range answers 206, and one past the end 416 with Content-Range: bytes */<size> and Drive’s requestedRangeNotSatisfiable error. Each upload records a revision, which revisions.list, get, update, delete and alt=media reach; a file created from metadata alone lists its head revision, as Drive does. files.export converts a Docs, Sheets or Slides file, and without mimeType answers 400 required “Required parameter: mimeType”. A resumable upload answers 501.

  4. files.list searches by q, sorts by orderBy, and fields selects the answer. q takes Drive’s grammar over name (=, !=, and contains matching the start of a word), mimeType, '<folder>' in parents, trashed, starred, modifiedTime, 'me' in owners and properties has, joined by and, or, not and parentheses; orderBy sorts by name, name_natural, folder, createdTime, modifiedTime and the other documented keys, each with desc; a pageSize outside 1 to 1000 answers 400 invalidParameter. A fields selector narrows every 2xx answer to the members it names, * for the whole resource, and leaves an error whole; without one a route answers Drive’s default members. about.get and every comments and replies method but delete answer 400 required without a selector, as Drive does.

  5. A caller’s access comes from its role on the file. A caller’s role is owner, or else the highest role a permission on the file or on a folder above it grants, an anyone link included: a file the caller holds no role on answers 404 notFound and is left out of files.list, a reader or commenter gets 403 insufficientFilePermissions on an update, permissions.list and a share, and only the owner deletes, trashes or restores. ownedByMe, capabilities, shared and permissionIds are worked out for each caller, and X-Rystic-Actor names which of a world’s users is calling. Only a comment’s or reply’s author changes or deletes it. Drive’s refusals of a query parameter answer as Drive does: useDomainAdminAccess=true on the permissions routes 404, sendNotificationEmail=true on an anyone grant 403, role owner without transferOwnership 403, includeItemsFromAllDrives=true without supportsAllDrives 403 supportsTeamDrivesRequired, and acknowledgeAbuse=true on a metadata read 403 invalidAbuseAcknowledgment.

  6. Shipped Drives to boot straight into. --scenario google-drive_media_vault boots Juno Park’s folders of images, video and proofs, shared with a collaborator, the world check.sh and the quickstart use. google-drive_shared_inbox holds files two other people shared with Noor Haddad, google-drive_trash_backlog a full bin beside one live doc, google-drive_team_workspace a team tree Dana Okafor owns and shares with a writer and a commenter, whose roles each answer as Drive’s do, and google-drive_fresh_mydrive an empty Drive; SCENARIOS.md says what each is for. In each, the default caller is the world’s own user, who owns what the world says they own, and about.get answers that user’s whole About. POST /_rystic/seed lands users, files, revisions with their bytes, permissions and comments directly, and POST /_rystic/reset rewinds the sequences and the clock, so two identical runs give byte-identical files.

  7. Measured against live Google Drive. The full-suite round of 2026-10-12 (20261012T222054Z) matched 271 of 308 probes against live Google Drive, none inconclusive. HOWTO.md states the differences this release ships with: seeded files and copies download 0 bytes, files.list answers each world’s My Drive root as a row, ownership does not move on transferOwnership, a request with no credential answers 401 where Drive answers 403 on a read, some q forms Drive takes are refused, a read-only file still answers canModifyContent: true, and some differences in the wire format.

Last updated on