Datastore HTTP API Documentation

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.

General information

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.

API compatibility

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.

Datastore identifiers

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:

    • Local IDs – These are IDs which are meaningful to the developer of the app, such as "default" or "settings". The scope of local IDs is the current user-app pair. These IDs are 1-64 characters containing only lowercase letters, digits, dot, hyphen or underscore, and they must not begin or end with dot. To create a local datastore, use the get_or_create_datastore endpoint.
    • Global IDs – These are IDs are not only unique for the user-app pair, they're also unique across Dropbox. These are more appropriate when treating each datastore as an individual document where users may create an unknown number of them. The IDs for global datastores are a dot followed by a dbase64 string of 1-63 characters. To create a global datastore, use the create_datastore endpoint. Note that once a global ID has been used, it cannot be issued again, even if deleted.

    Note: Some datastore SDKs enforce a limit of only 32 characters for local 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 local datastore is deleted and recreated, it is assigned a new handle.

  • Title – A title is stored as part of the datastore metadata (explained in more detail below). The title is optional and should be used as the human readable name of a datastore. Unlike IDs and handles, the title can be edited after the datastore has been created. It can also be accessed without having to first load the entire datastore, making it particularly useful for labels in a list of datastore-based documents.

Changes and values

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 local 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 ID ':info', 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:

  • LIST_PUT replaces an existing list item with a new atomic value
  • LIST_INSERT inserts a new atomic value into a list
  • LIST_DELETE deletes an existing list item
  • LIST_MOVE moves an existing list item to a new position

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:

  • integer (64-bit signed)
  • floating point (64-bit IEEE double precision, including NaN and ± infinity)
  • Boolean
  • string (UTF-8-encoded Unicode code points)
  • bytes (sequence of arbitrary 8-bit bytes)
  • timestamp (milliseconds since 1/1/1970 UTC)

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

Metadata

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:

  • title
    An optional human-readable title for the document. This does not need to be unique across datastores.
  • mtime
    An optional timestamp giving the date/time of the last modification. It is up to the app to decide when to set this and to what value.

Formal grammar for deltas and snapshots

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:

  • A hash sign (#) 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

Standard error responses

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:

  • If a datastore is not found or access to it is not allowed to the requesting user, a JSON-encoded dictionary is returned of the form <notfound_result> (see below).
  • If the put_delta endpoint detects a conflict, it returns a JSON-encoded dictionary of the form <conflict_result> (see below).

These results are defined as follows:

<notfound_result>  ::= {"notfound": <str>}  # error message
<conflict_result>  ::= {"conflict": <str>}  # error message

The error messages are designed to be understandable for developers but not suitable for end users.

Endpoints

/datastores/list_datastores

Description
Retrieves a list of all datastores for the current user.
URL structure
https://api.dropbox.com/1/datastores/list_datastores
Method
GET (or POST)
Parameters
None
Returns

<list_datastores_result> ::= {"datastores": <datastores>, "token": <dbase64>}
<datastores>             ::= [<dsinfo>, ...]
<dsinfo>                 ::= {"dsid": <str>, "handle": <handle>,
                              "rev": <rev>, "info": <infodict>}
<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 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.

/datastores/get_datastore

Description
Checks the validity of a datastore ID and returns its revision and handle. The ID must refer to an existing datastore. This works for both local and global datastores.
URL Structure
https://api.dropbox.com/1/datastores/get_datastore
Method
GET (or POST)
Parameters
dsid
the datastore ID to check
Returns

<get_result> ::= {"rev": <rev>, "handle": <handle>}

Errors

<notfound_result>

/datastores/get_or_create_datastore

Description
Checks the validity of a local datastore ID or creates a new datastore with the given ID, and returns its revision and handle. This only works for local datastores. To create a global datastore, use create_datastore.
URL Structure
https://api.dropbox.com/1/datastores/get_or_create_datastore
Method
POST
Parameters
dsid
string giving the datastore ID
Returns

<get_or_create_result> ::= {"rev": <rev>, "handle": <handle>,
                            "created": <Boolean>}

/datastores/create_datastore

Description
Creates a new "global" datastore and returns its revision and handle. If the datastore already exist and is owned by the same user, this call returns its revision and handle. If the datastore already exists and is owned by another user, this call returns an error. To create a local datastore, use get_or_create_datastore.
URL Structure
https://api.dropbox.com/1/datastores/create_datastore
Method
POST
Parameters
dsid
string giving a new global datastore ID
key
dbase64 string to be used as a "seed" for the datastore ID

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.

Returns

<get_or_create_result> ::= {"rev": <rev>, "handle": <handle>,
                            "created": <Boolean>}

Errors

<notfound_result>

/datastores/delete_datastore

Description
Remove a datastore. Once this operation is executed successfully all future references to the given handle will return a <notfound_result> error.
URL Structure
https://api.dropbox.com/1/datastores/delete_datastore
Method
POST
Parameters
handle
the handle of an existing datastore
Returns

<delete_result> ::= {"ok": <str>}  # confirmation message

Errors

<notfound_result>

/datastores/get_deltas

Description
Return deltas for a datastore since a given revision.
URL Structure
https://api.dropbox.com/1/datastores/get_deltas
Method
GET (or POST)
Parameters
handle
the handle of an existing datastore
rev
the revision from which to start
Returns

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

Errors

<notfound_result>

/datastores/put_delta

Description
Write a delta to the server, if the client is up to date.
URL Structure
https://api.dropbox.com/1/datastores/put_delta
Method
POST
Parameters
handle
the handle of an existing datastore
rev
the revision to which to apply the delta
nonce
optional dbase64 string (up to 100 characters) used to uniquely identify this delta
changes
JSON-encoded <list_of_changes>
Returns

<put_result> ::= {"rev": <rev>}

Errors

<notfound_result>
<conflict_result>

/datastores/get_snapshot

Description
Return a full snapshot of a datastore.
URL Structure
https://api.dropbox.com/1/datastores/get_snapshot
Method
GET (or POST)
Parameters
handle
the handle of an existing datastore
Returns

<snapshot_result> ::= {"rows": <list_of_rows>, "rev": <rev>}
<list_of_rows>    ::= [<row>, ...]
<row>             ::= {"tid": <tid>, "rowid": <recordid>, "data": <datadict>}

Note: This response uses row(s) instead of record(s).

Errors

<notfound_result>

/datastores/await

Description
This is a "long poll" request that blocks up to a minute or until a change is detected.
URL Structure
https://api.dropbox.com/1/datastores/await
Method
GET or POST
Parameters
get_deltas
optional <get_deltas_arg> (see below)
list_datastores
optional <list_datastores_arg> (see below)

<get_deltas_arg>      ::= {"cursors": <cursordict>}
<cursordict>          ::= {<handle>: <rev>, ...}
<list_datastores_arg> ::= {"token": <dbase64>}

Returns

<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:

  • "get_deltas" is present if any of the datastores whose handle was specified in the request has a change or is invalid or deleted. In this case the <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" is present if a non-empty token was specified in the request and a list_datastores request would return a different token. In that case the full <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.