All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.8.0 - 2026-10-01
Added
Storage.list_page/3andStorage.continue_list/3with typedMagpie.ListPageresults for bounded listings, external checkpoints and later change pollingFiles.ListFolder.get_latest_cursor/3accepts listing options, including recursive cursors for future changes without fetching an initial listing; the existing two-argument call remains unchangedMagpie.CursorErrorpreserves Dropbox reset diagnostics and explicitly requires state reconstruction without silently replacing a saved cursor; malformed-cursor HTTP 400 responses remainMagpie.Errorbecause there is no stable reset tag- framework-independent
Magpie.Webhookchallenge responses, HMAC-SHA256 verification of original body bytes with constant-time digest comparison, and account notification decoding; unsupported signed JSON objects are acknowledged as:ignoredwithout enqueueing - optional
Magpie.Webhook.Plugendpoint before body parsers, with bounded raw body reading and application-owned enqueue callbacks; empty account lists skip enqueueing and invalid resolved secrets/callback returns raise explicit errors - incremental scanner, recovery, Phoenix/Plug, optional Oban and consumer testing recipes, with executable scanner examples and offline regression coverage
Changed
- additive release: existing list, stream and low-level endpoint contracts remain unchanged; no new runtime dependency or minimum version change
- cursors, account serialization, persistence and idempotent processing remain the consumer's responsibility; no synchronization engine or event history
0.7.2 - 2026-09-21
Fixed
Magpie.Files.ListFolder.longpoll/3reached a route that does not exist on the RPC host: Dropbox serves the longpoll fromnotify.dropboxapi.comand rejects it when the request carries anAuthorizationheader- the longpoll now waits for the requested timeout plus the jitter Dropbox
adds, instead of giving up at the configured
receive_timeout
Added
is_restorableonMagpie.FileMetadata, filled in by calls that ask for"include_restorable_info" => true- a timeout argument on
longpoll/3(30 to 480 seconds, default 30) :notify_urlclient option, also settable withconfig :magpie, notify_url:
0.7.1 - 2026-09-20
Added
- runnable order inbox example with CSV validation, duplicate prevention, persisted receipts, and recovery after interrupted report/archive work
- verified backup example with revision manifests, restore drills, integrity checks, and a retention plan that leaves deletion to the operator
- document search example with SQLite full-text indexing, ranked excerpts, saved Dropbox cursors, deletion handling, and transactional rebuilds
- offline demos, tests, English guides, and CI checks for all three examples
Documentation
- link the standalone example projects from the README
0.7.0 - 2026-09-19
Client-specific settings and stricter option validation.
Added
Client.new/2andClient.with_options/2for isolated HTTP options, endpoint URLs, retry policy and cooperative execution budgets;request: [...]overrides on all Storage operations- execution budgets shared across Storage listing pages and upload sessions,
with
%Magpie.TimeoutError{}when the budget runs out - per-token-server OAuth
req_options, also available asoauth_req_optionswhen a client creates its own token server - API error
endpoint,attempts, andretry_after(milliseconds),Error.diagnostics/1,Error.required_scope/1, and localClient.missing_scopes/2checks that distinguish unknown grants - account labels, attempt counts and retry delays in request Telemetry
- configuration and testing guides, real HTTP loopback tests, and an opt-in Dropbox contract test for a dedicated account
- CI test matrix for Elixir/OTP 1.15.8/26.2, 1.18.4/27.3 and 1.20/29
Changed
- Storage and high-level Files uploads reject invalid and unsupported options before I/O; invalid common batch options fail before starting workers, while per-item failures remain isolated. See the upgrading guide.
- retry and HTTP configuration precedence is operation, client, then application; retry policies replace rather than merge, HTTP options merge by key
- file streaming selects the appropriate API for Elixir 1.15 and newer versions
Write defaults remain mode: "add", autorename: true. Mutations and downloads
streamed to disk do not gain automatic transient retries.
0.6.3 - 2026-09-13
Fixed
redact token server state and sensitive messages from OTP status diagnostics
preserve upload failures without attempting subsequent local hash reads
preserve exception tuples from generic pagination callbacks
add regression coverage for continuation-page API/transport failures and download destination preservation and temporary-file cleanup
conditional Storage uploads always enforce
if_rev, even withskip_unchanged: true; validate revisions before metadata lookupshide credentials in client and OAuth token inspection, including custom token provider arguments
avoid echoing credential options in validation errors and callback exception messages in token refresh logs; retain the exception type for diagnosis
0.6.2 - 2026-09-08
Documentation
- require 0.6.1 or newer in installation examples and document the 0.6.0
Storage.list/3regression - correct batch and bang-variant examples, including non-exception batch errors
- document retry scope, transfer telemetry and upload/download progress options
- align public types with integrity and transport errors
0.6.1 - 2026-09-08
Fixed
Magpie.Storage.list/3returns Dropbox API errors raised while paginating as{:error, %Magpie.Error{}}again, as 0.5.1 did, instead of letting them escape to the caller
0.6.0 - 2026-09-08
Production-ready object storage workflows.
Added
Magpie.Storage.copy/4,move/4andmkdir/3, with bang variants- concurrent
put_many/3anddelete_many/3with stable result order, per-item isolation, timeouts and progress callbacks - upload integrity checks with
verify: true, unchanged-object detection withskip_unchanged: true, and%Magpie.IntegrityError{} - optimistic concurrency through
if_rev: rev - upload/download progress callbacks
- request
start,stop,exceptionandretry, plus transfer progress, Telemetry events - Dropbox's
X-Dropbox-Request-Idon%Magpie.Error{request_id: ...}
Changed
- normal
Magpie.Storagecalls return expected Req transport failures as error tuples; bang variants and lazy streams retain raising semantics - known read-only Dropbox POST routes retry transient transport errors, 429s
and selected 5xx responses, respecting
Retry-Afterand otherwise using exponential backoff with jitter; mutation routes are never retried blindly
0.5.1 - 2026-09-06
Storage reliability and documentation improvements.
Fixed
Magpie.Storage.list/3now returns Req transport failures as{:error, exception}instead of crashing the caller
Documentation
- Added a complete
Magpie.Storageworkflow to the examples guide
0.5.0 - 2026-09-06
Storage ergonomics. Applications can now use Dropbox through a compact,
object-storage-style API while the complete Dropbox-specific surface remains
available under Magpie.Files and the other namespace modules.
Added
Magpie.Storagewithput,get, streamingdownload,delete,exists?,stat, eagerlist, lazystream, temporary download URLs and one-use upload URLs- Explicit upload sources:
{:file, path},{:binary, iodata}and{:stream, enumerable}; streams use upload sessions and are rechunked without accumulating the whole input in memory - Bang variants for storage operations, convenient for scripts
Magpie.Files.upload_data/4,upload_stream/4anddownload_file/3Magpie.Error.not_found?/1,conflict?/1,rate_limited?/1,auth?/1andretryable?/1
Changed
- Large file uploads now send their final partial chunk with the upload session commit request
- The public roadmap and its README link were removed
0.4.0 - 2026-09-04
Typed metadata. The files endpoints now describe files and folders with
structs instead of raw JSON maps with ".tag" keys. This changes the shape
of several results — the Upgrading to 0.4 guide lists
every affected call with the 0.3 and 0.4 versions side by side.
Added
Magpie.FileMetadata,Magpie.FolderMetadataandMagpie.DeletedMetadata— typed structs for the three kinds of entry Dropbox returns, withDateTimetimestamps (client_modified,server_modified), a first-classcontent_hash,is_downloadabledefaulting totrue, and the nested objects (sharing_info,media_info,file_lock_info, ...) kept as raw mapsMagpie.Metadata— the decoder behind it:decode/2(by".tag", or by an explicit:file/:folderkind for the endpoints Dropbox answers untagged),unwrap/2for%{"metadata" => ...}results,decode_page/2for listings anddecode_matches/1for search pages, so endpoints called by hand throughMagpie.post/3can be decoded the same wayMagpie.Metadata.content_hash/1— computes Dropbox's block-wise SHA-256 content hash of a binary or a stream of chunks, to verify uploads and downloads againstMagpie.FileMetadata.content_hash- Upgrading guide on HexDocs
Changed
- Breaking:
Magpie.Files.get_metadata/5,upload/6,upload_file/4,restore/3,Magpie.Files.UploadSession.finish/8andfinish_data/5return metadata structs instead of maps;Magpie.LiveView.UploadWriter'smeta/1carries the struct under:metadata - Breaking:
Magpie.Files.create_folder/2,delete_folder/2,copy/3andmove/3return the struct directly — the%{"metadata" => ...}envelope is gone - Breaking:
Magpie.Files.ListFolder.list_folder/3,list_folder_continue/2,stream/3andlist_revisions/4decode every entry; the page itself ("cursor","has_more","is_deleted") keeps its string keys - Breaking:
Magpie.Files.search/3,search_continue/2andsearch_stream/3decode each match's"metadata"into a struct, flattening Dropbox's one-variant%{".tag" => "metadata", "metadata" => ...}union - Req requirement bumped to
~> 0.7.4, andjason— which Magpie always used to build theDropbox-API-Argheader — is now a declared dependency instead of one inherited from Req
Deprecated
Magpie.Files.create_folder_to_struct/2,delete_folder_to_struct/2and theMagpie.Folderstruct they build —create_folder/2anddelete_folder/2return the richer typed metadata themselves now
0.3.2 - 2026-08-18
Added
- Phoenix & LiveView uploads guide on HexDocs — controllers,
Magpie.LiveView.UploadWriter, direct browser → Dropbox uploads withpresign_upload/4, and how to test both offline ROADMAP.md— planned work moved out of the README
0.3.1 - 2026-08-14
Tracks the June–July 2026 Dropbox API spec updates that touch routes Magpie already wraps.
Added
Magpie.Files.ListFolder.list_folder/3andstream/3now take an optionaloptsmap merged into the request body, so callers can use the remaining/files/list_folderarguments — including the newinclude_restorable_infoflag (each returned deleted entry then says whether it can be restored viais_restorable)Magpie.Files.ListFolder.list_revisions/4gained the same optionaloptsmap ("mode","before_rev","include_restorable_info")
Changed
Magpie.Files.get_thumbnail_v2/3docs no longer list"quality"as an option — Dropbox pulled the field from the public API surface in the July 2026 spec update
0.3.0 - 2026-08-10
Phoenix uploads. LiveView already owns the upload experience, so Magpie does not ship a component — it fills the two gaps a Dropbox backend creates: getting the bytes there without spooling them to disk, and skipping the server altogether.
Added
Magpie.LiveView.UploadWriter— aPhoenix.LiveView.UploadWriterthat streams a LiveView upload straight into a Dropbox upload session, so the bytes never land on the server's disk. Chunks are buffered to:chunk_size(default 8 MiB, Dropbox wants multiples of 4 MiB) and the tail rides along with the finish call. Magpie does not depend on:phoenix_live_view— the behaviour is a plain set of callbacksMagpie.LiveView.presign_upload/4— a LiveView:externaluploader that mints a one-time link withMagpie.Files.get_temporary_upload_link/3so the browser posts the file straight to Dropbox, bypassing the server. Entries above Dropbox's 150 MB single-request limit are rejected at presign time instead of being handed a link that cannot work. The client-side half ships aspriv/static/magpie_uploader.js
0.2.1 - 2026-08-05
Added
Magpie.Auth.TokenServerno longer requires a refresh token at startup. A server started without one sits in an unconfigured state — calls return a pattern-matchable{:error, %Magpie.Error{summary: "no_refresh_token"}}without touching the network — and the newset_refresh_token/3configures it (or replaces the token, for re-authorization) at any time, discarding any cached access token unless a validaccess_token/expires_atpair is seeded. Fresh installs whose token arrives through the OAuth callback now work from a plain static supervision treeMagpie.Auth.authorize_url/2acceptsextra_params:(keyword list or map) for additional Dropbox authorization params such asforce_reapprove,locale,require_roleanddisable_signup. Params the function already sets cannot be overridden — collisions raiseArgumentError
0.2.0 - 2026-08-05
OAuth 2 support. Dropbox access tokens expire after ~4 hours, so a static token is not enough for anything that runs unattended — Magpie now handles the whole flow and keeps tokens fresh on its own.
Added
Magpie.Auth— OAuth 2 flow helpers:authorize_url/2(offline access by default),pkce_pair/0andpkce_challenge/1for public apps,exchange_code/3andrefresh/3Magpie.Auth.Token— token struct with an absoluteexpires_atcomputed from Dropbox'sexpires_inMagpie.Auth.TokenProvider— behaviour that decouples the client from where tokens live, with two implementations:Magpie.Auth.StaticTokenandMagpie.Auth.TokenServerMagpie.Auth.TokenServer— supervised token holder that refreshes proactively (configurable:refresh_margin, default 300s), serializes concurrent refreshes into a single request, survives failed refreshes and can persist tokens through an:on_refreshcallbackMagpie.Client.new/1accepts a refresh token (refresh_token:/app_key:+app_secret:orpkce: true, starting a linkedTokenServer) or an explicittoken_provider: {module, arg}- Transparent recovery from expired tokens: requests rejected with HTTP 401
expired_access_tokenare refreshed and replayed once. Streamed upload bodies cannot be replayed and are not retried — the proactive refresh covers them - OAuth guide on HexDocs: getting a refresh token from the App Console, the web redirect flow, PKCE, supervision, persistence and custom providers
Changed
Magpie.Clientgained atoken_providerfield;access_tokenis kept andMagpie.Client.new("ACCESS_TOKEN")behaves exactly as beforeMagpie.Error.new/2uses the OAutherrorfield (e.g."invalid_grant") as the errorsummarywhen there is noerror_summary
0.1.0 - 2026-08-01
First release of Magpie 🐦 — a modern, actively maintained Elixir client for the Dropbox API v2, born as a rewrite of the unmaintained elixir_dropbox package (see the Origin section of the README).
Added
- Coverage of all 132 current user-scoped routes of the Dropbox API v2
(
files,sharing,file_properties,file_requests,users,account,auth,check,contacts,openid), verified against the official dropbox-api-spec Magpie.Files.upload_file/4— smart upload: single request for small files, chunked upload session for large ones, streamed from diskMagpie.Pager— lazyStream-based pagination, with ready-made wrappers (Magpie.Files.ListFolder.stream/2,Magpie.Files.search_stream/3,Magpie.Sharing.list_folders_stream/2,Magpie.FileRequests.stream/2)Magpie.Async.await/4— waits for asynchronous batch jobs by polling the check endpoint with exponential backoffMagpie.Error— normalized error struct (and exception) carrying the HTTPstatus, Dropbox'serror_summaryand the full errorbody- Offline test suite built on
Req.Test(~94% line coverage) and aconfig :magpie, req_options: [...]hook so consumer apps can stub Dropbox in their own tests - Examples guide on HexDocs
Changed
- HTTP client migrated from HTTPoison/Poison to Req/Jason
- Every call now returns
{:ok, result}or{:error, %Magpie.Error{}} - Deprecated Dropbox endpoints migrated to their current versions:
move_v2,search_v2(+search/continue_v2),copy_batch_v2(+check_v2),upload_session/finish_batch_v2andcreate_shared_link_with_settings - Default endpoint URLs are built in — consumer configuration is optional
Fixed
Magpie.Files.upload/6sent the file as JSON instead of raw bytes, breaking every upload since the Req migrationMagpie.Users.get_account_to_struct/2always returned an error even on successful responses- Paper
docs/users/list/continuepointed to a nonexistent URL
Deprecated
- The legacy
/paper/docs/*wrappers (Magpie.Paper.*) remain for compatibility, but the whole Paper API is deprecated by Dropbox — preferMagpie.Files.Paper