The Dropbox Datastore API supports synchronized storage of per-user structured data across multiple platforms and devices, such as app settings, bookmarks, or game state.
SDKs for using the Datastore API are provided for use in Objective C for iOS, in Java for Android, in JavaScript for use in web browsers, and in Python for use on servers.
In order to access the Datastore API in other languages or environments, you can call the HTTP endpoints used by the SDKs. The HTTP endpoints for the Datastore API behave similar to the HTTP endpoints for manipulating files in the Core API. In particular, you need an OAuth access token — OAuth2 is recommended. The rest of this page documents the endpoints specific to the Datastore API and their behavior.
For a general introduction to datastores, see the Datastore API documentation for any of the supported client platforms. Important concepts referenced here include Datastore, Table, Record, Field, and List, which together form the data model used to discuss datastores and their operations here.
The term dbase64-encoded bytes refers to a string that encodes a sequence of bytes using a variant of the common urlsafe Base64 encoding, omitting trailing '=' signs and disallowing embedded whitespace. This encoding uses lowercase and uppercase letters, digits, hyphen, and underscore (the hyphen and underscore replace the plus and slash in the standard Base64 alphabet). The term dbase64 string refers to a string restricted to the same character set, but not necessarily forming a canonical encoding of a sequence of bytes.
This API will evolve. Future versions of this API may add new endpoints or parameters. In order to keep older clients working, the behavior and return value of APIs with given parameter values will not change from the currently documented behavior and return values, with two important exceptions: currently undocumented request parameters (whether they are actually ignored or not) may be given a specific meaning, and objects returned in responses may contain additional keys in the future.
Thus, clients that want to be future-proof should avoid passing undocumented parameters (as they may cause different behavior in the future), and they should avoid strict checks on the keys of objects found in responses.
For example, you should not consider a /list_datastores response invalid if it contains additional keys in the <dsinfo>
or <infodict>
in addition to the keys currently documented below for these objects.
Apps may want to share data between users. With the Datastore API, apps can share a datastore across multiple Dropbox accounts. The unit of sharing is a single datastore, and one or more datastores may be shared between accounts. Any datastore with a shareable ID (see "Datastore identifiers" below) can be shared by assigning roles to principals, creating an access control list. Any Dropbox account with the correct permissions will then be able to open the shared datastore by ID.
There are two available principals to whom you may apply a role:
There are four available roles:
An individual datastore has a few different types of identifiers that are used for different purposes. Apps typically use a subset of identifiers, but the useful ones will vary by use case.
ID – The datastore's ID is a string that uniquely identifies the datastore for a given user-app pair across all the user's devices. There are actually two types of datastore IDs:
get_or_create_datastore
endpoint.create_datastore
endpoint. Note that once a shareable ID has been used, it cannot be issued again, even if deleted.Note: Some outdated datastore SDKs enforce a limit of only 32 characters for private datastore IDs. To interoperate with these SDKs, consider constraining your IDs to the smaller 32-character limit.
Handle – A datastore also has a handle which is a string used as an internal identifier, assigned to it by the server. Handles are managed internally in the Datastore SDKs but code that uses the HTTP endpoints directly must pass the handle as a parameter for API calls that read or write to a datastore. A handle is a dbase64 string of 1-1000 characters. When a datastore with a private ID is deleted and recreated, it is assigned a new handle.
The objects described in this section are all represented as JSON in the HTTP API. A formal grammar is included in the next section, but first we introduce the concepts.
A snapshot refers to the state of a datastore at a given point in time. It refers to a specific set of tables, records, and fields that make up the content of the datastore at that time.
The revision is an integer indicating a specific version or snapshot of a datastore. Each time the server successfully accepts a put_delta
request it increments the revision by one (or occasionally more). A newly created datastore has revision 0; when a private datastore is deleted and recreated its revision is reset to 0.
A delta describes a list of changes to a datastore. A delta has an associated revision, which identifies the snapshot to which the delta should be applied. A delta also has an optional nonce field. This is different from a cryptographic nonce; it is simply a string used to distinguish deltas originating from the current client from deltas sent by other clients. (This is useful in case the server accepted a delta but the client didn't receive the 'success' response due to some failure.)
A change describes a record-level operation; it can be a record insertion, a record update, or a record deletion. Record insertions and updates can reference multiple fields. A change refers to a record by its table ID and record ID. An insertion specifies the initial contents of the record as a dictionary mapping field names to values. An update specifies the changes as a dictionary mapping field names to operations.
Table IDs, record IDs, and field names are all strings of 1-64 characters from the following set: lowercase and uppercase letters, digits, dot, hyphen, underscore, plus, slash, equal. Reserved IDs/names starting with colon are also allowed; the general form of these is a colon followed by 1-63 characters from the above set. The server currently only accepts the reserved table IDs ':info' and ':acl', but it may start returning and/or accepting other reserved IDs/names in the future.
Note: Some datastore SDKs enforce a limit of only 32 characters for table IDs, record IDs, and field names. To interoperate with these SDKs, consider constraining your IDs and field names to the smaller 32-character limit.
An operation represents a change to a single field. Whole-field operations can PUT a new value into a field (either creating the field or replacing a previous value) or DELETE a previously existing field. List operations come in five flavors. LIST_CREATE creates a field containing an empty list value. The remaining four flavors require an existing field whose value is a list:
These four all take a 0-based index indicating the position for the value to insert/replace/delete; LIST_PUT and LIST_INSERT additionally take an atomic value; LIST_MOVE takes a second position. All indexes must be >= 0. For LIST_INSERT, the index must be less than or equal to the length of the list; for all other operations the index must be strictly less than the length of the list.
A value is either a list of atoms or a single atom. Lists cannot contain other lists, only atoms.
An atom can be a primitive value of one of the following types:
There's no size limit on a field inside the record, however the overall record is limited to 100 x 1024 bytes (a.k.a. 100 KiB).
Datastores may have metadata associated with them which, if present, will be returned by list_datastores
in the <infodict>
. Metadata is represented in the datastore itself in the :info
table, as fields of the info
record. The server restricts updates to this table to known metadata items. Currently defined metadata items are:
Shareable datastores can include an :acl
table, which represents the access control list for the datastore. Each record in this table maps a principal to a role. The record ID names the principal (either public
or team
), and the role
field is an integer mapping to a predifined role: 1000
for viewer and 2000
for editor.
Some calls, such as list_datastores return the authenticated user's effective role. In this case, the role can be 1000
or 2000
as above, but also 3000
, which indicates that the user is the owner of the datastore. The creator of a datastore is always that datastore's owner, so the owner role cannot be assigned.
Datastores have limits on their maximum size to ensure good performance across platforms. You should keep these in mind as guidelines when modeling your datastores.
Your app can store up to 5MB of data across all its datastores without counting against the user's storage quota. Any data beyond the first 5MB is factored into the user's Dropbox storage quota, and writing can be limited in these cases when a user is over quota. Sizes are calculated as:
Maximum record size | 100 KiB |
Maximum number of records per datastore | 100,000 |
Maximum datastore size | 10 MiB |
Maximum size of a delta | 2 MiB |
Here is a formal grammar describing the details of how deltas and snapshots are encoded as JSON. Please refer to the previous section for the semantics. The grammar has multiple roots, referenced from various API endpoints described in later sections.
Notes on the notation:
#
) introduces a comment.[...]
specifies a JSON list, not an optional grammar production. Lists with a variable number of items are shown as [<item>, ...]
. These may be empty.{...}
specifies a JSON dictionary. The key order is not constrained by the grammar. Dictionaries with a variable number of items are shown as {<key>: <value>, ...}
. These may be empty.Here is the grammar:
<list_of_deltas> ::= [<delta>, ...] # ordered by rev <delta> ::= {"rev": <rev>, "changes": <list_of_changes>, "nonce": <dbase64>} # nonce is optional <list_of_changes> ::= [<change>, ...] # in the order in which they should be applied <change> ::= ["I", <tid>, <recordid>, <datadict>] # INSERT | ["U", <tid>, <recordid>, <opdict>] # UPDATE | ["D", <tid>, <recordid>] # DELETE <datadict> ::= {<field>: <value>, ...} <opdict> ::= {<field>: <fieldop>, ...} <field> ::= <id> <tid> ::= <id> <recordid> ::= <id> <id> ::= <str> # see constraints above <fieldop> ::= ["P", <value>] # PUT | ["D"] # DELETE | ["LC"] # LIST_CREATE | ["LP", <index>, <atom>] # LIST_PUT | ["LI", <index>, <atom>] # LIST_INSERT | ["LD", <index>] # LIST_DELETE | ["LM", <index>, <index>] # LIST_MOVE <index> ::= <int> <value> ::= <atom> | [<atom>, ...] <atom> ::= <Boolean> | <str> # always means UTF-8-encoded text string | <number> # always means floating point | <wrapped_int> # must be used to represent integers | <wrapped_special> # used for NaN and infinities | <wrapped_timestamp> | <wrapped_bytes> <wrapped_int> ::= {"I": <str>} # decimal representation of a signed 64-bit int <wrapped_special> ::= {"N": "nan"} | {"N": "+inf"} | {"N": "-inf"} <wrapped_timestamp> ::= {"T" : <str>} # decimal representation of a signed 64-bit int <wrapped_bytes> ::= {"B" : <dbase64>} # dbase64-encoded bytes <rev> ::= <int> <handle> ::= <dbase64> <dbase64> ::= <str> # dbase64 string <number> ::= <int> | <float> <Boolean> ::= # standard JSON Boolean (false or true) <str> ::= # standard JSON string <int> ::= # standard JSON integer <float> ::= # standard JSON float <role> ::= 1000 # viewer | 2000 # editor | 3000 # owner
Most invalid requests (e.g. for invalid parameter values) return an HTTP status of 400 or 404; authentication errors return an HTTP status of 401 or 403.
Some application-level errors return an HTTP status of 200 with a JSON-encoded dictionary in the body. These include:
<notfound_result>
(see below).<conflict_result>
(see below).These results are defined as follows:
<notfound_result> ::= {"notfound": <str>} # error message <conflict_result> ::= {"conflict": <str>} # error message <access_denied_result> ::= {"access_denied": <str>} # error message
The error messages are designed to be understandable for developers but not suitable for end users.
https://api.dropbox.com/1/datastores/list_datastores
<list_datastores_result> ::= {"datastores": <datastores>, "token": <dbase64>} <datastores> ::= [<dsinfo>, ...] <dsinfo> ::= {"dsid": <str>, "handle": <handle>, "rev": <rev>, "info": <infodict>, "role": <role>} <infodict> ::= {"title": <str>, "mtime": <wrapped_timestamp>}
The <infodict>
dict reflects the contents of the datastore's metadata and is omitted if no metadata is present in the datastore; even if present, individual keys are omitted if the corresponding metadata field is not set in the datastore.
The role
field is only present for shareable datastores.
The token is a dbase64 string which represents a hash of the <datastores>
list, exclusive of the rev and mtime values. It can be passed to the await
endpoint.
https://api.dropbox.com/1/datastores/get_datastore
<get_result> ::= {"rev": <rev>, "handle": <handle>, "role": <role>}
Note: The role
field is only returned for shareable datastores.
<notfound_result>
create_datastore
.https://api.dropbox.com/1/datastores/get_or_create_datastore
<get_or_create_result> ::= {"rev": <rev>, "handle": <handle>, "created": <Boolean>}
get_datastore
with the shareable ID. To create a datastore with a private ID, use get_or_create_datastore
.https://api.dropbox.com/1/datastores/create_datastore
The key and dsid parameters must satisfy the following relationship:
dsid == '.' + dbase64(sha256(key))
Note that the SHA-256 hash is taken directly from the dbase64 key string (the key is not decoded to a raw string of bytes), and the datastore ID is formed by dbase64-encoding the bytes of the SHA-256 hash digest.
<get_or_create_result> ::= {"rev": <rev>, "handle": <handle>, "created": <Boolean>, "role": <role>}
<notfound_result>
<notfound_result>
error.https://api.dropbox.com/1/datastores/delete_datastore
<delete_result> ::= {"ok": <str>} # confirmation message
<notfound_result>
https://api.dropbox.com/1/datastores/get_deltas
<get_deltas_result> ::= {"deltas": <list_of_deltas>}
If no deltas newer than the rev parameter are known to the server, this will return an empty list. If the full response would exceed the size limit not all deltas known to the server will be returned: older deltas (i.e. with lower revisions) will be returned first, and at least one delta will be returned.
<notfound_result>
https://api.dropbox.com/1/datastores/put_delta
<put_result> ::= {"rev": <rev>}
<notfound_result> <conflict_result> <access_denied_result>
https://api.dropbox.com/1/datastores/get_snapshot
<snapshot_result> ::= {"rows": <list_of_rows>, "rev": <rev>, "role": <role>} <list_of_rows> ::= [<row>, ...] <row> ::= {"tid": <tid>, "rowid": <recordid>, "data": <datadict>}
Note: This response uses row(s) instead of record(s).
<notfound_result>
https://api.dropbox.com/1/datastores/await
<get_deltas_arg>
(see below)<list_datastores_arg>
(see below)<get_deltas_arg> ::= {"cursors": <cursordict>} <cursordict> ::= {<handle>: <rev>, ...} <list_datastores_arg> ::= {"token": <dbase64>}
<await_result> ::= {"get_deltas": <await_deltas_result>, "list_datastores": <list_datastores_result>} <await_deltas_result> ::= {"deltas": <datastores_map>} <datastores_map> ::= {<handle>: <per_datastore_result>, ...} <per_datastore_result> ::= <get_deltas_result> | <notfound_result>
The "get_deltas" and "list_datastores" keys in the top-level result are optional:
<datastores_map>
has a <get_deltas_result>
entry for every such handle that has a new revision. In addition, for handles that are invalid or deleted, it has a <notfound_result>
entry.<list_datastores>
response is returned.The request returns when either of these conditions is true, or if approximately a minute passes. In the latter case an empty dictionary is returned.