Tech-invite3GPPspaceIETF RFCsSIP
in Index   Prev   Next

RFC 8620

The JSON Meta Application Protocol (JMAP)

Pages: 90
Proposed Standard
Part 2 of 5 – Pages 16 to 28
First   Prev   Next

Top   ToC   RFC8620 - Page 16   prevText

3. Structured Data Exchange

The client may make an API request to the server to get or set structured data. This request consists of an ordered series of method calls. These are processed by the server, which then returns an ordered series of responses.

3.1. Making an API Request

To make an API request, the client makes an authenticated POST request to the API resource, which is defined by the "apiUrl" property in the Session object (see Section 2). The request MUST be of type "application/json" and consist of a single JSON-encoded "Request" object, as defined in Section 3.3. If successful, the response MUST also be of type "application/json" and consist of a single "Response" object, as defined in Section 3.4.

3.2. The Invocation Data Type

Method calls and responses are represented by the *Invocation* data type. This is a tuple, represented as a JSON array containing three elements: 1. A "String" *name* of the method to call or of the response. 2. A "String[*]" object containing named *arguments* for that method or response. 3. A "String" *method call id*: an arbitrary string from the client to be echoed back with the responses emitted by that method call (a method may return 1 or more responses, as it may make implicit calls to other methods; all responses initiated by this method call get the same method call id in the response).

3.3. The Request Object

A *Request* object has the following properties: o using: "String[]" The set of capabilities the client wishes to use. The client MAY include capability identifiers even if the method calls it makes do not utilise those capabilities. The server advertises the set of specifications it supports in the Session object (see Section 2), as keys on the "capabilities" property.
Top   ToC   RFC8620 - Page 17
   o  methodCalls: "Invocation[]"

      An array of method calls to process on the server.  The method
      calls MUST be processed sequentially, in order.

   o  createdIds: "Id[Id]" (optional)

      A map of a (client-specified) creation id to the id the server
      assigned when a record was successfully created.

      As described later in this specification, some records may have a
      property that contains the id of another record.  To allow more
      efficient network usage, you can set this property to reference a
      record created earlier in the same API request.  Since the real id
      is unknown when the request is created, the client can instead
      specify the creation id it assigned, prefixed with a "#" (see
      Section 5.3 for more details).

      As the server processes API requests, any time it successfully
      creates a new record, it adds the creation id to this map (see the
      "create" argument to /set in Section 5.3), with the server-
      assigned real id as the value.  If it comes across a reference to
      a creation id in a create/update, it looks it up in the map and
      replaces the reference with the real id, if found.

      The client can pass an initial value for this map as the
      "createdIds" property of the Request object.  This may be an empty
      object.  If given in the request, the response will also include a
      createdIds property.  This allows proxy servers to easily split a
      JMAP request into multiple JMAP requests to send to different
      servers.  For example, it could send the first two method calls to
      server A, then the third to server B, before sending the fourth to
      server A again.  By passing the createdIds of the previous
      response to the next request, it can ensure all of these still
      resolve.  See Section 5.8 for further discussion of proxy

   Future specifications MAY add further properties to the Request
   object to extend the semantics.  To ensure forwards compatibility, a
   server MUST ignore any other properties it does not understand on the
   JMAP Request object.
Top   ToC   RFC8620 - Page 18

3.3.1. Example Request

{ "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], "methodCalls": [ [ "method1", { "arg1": "arg1data", "arg2": "arg2data" }, "c1" ], [ "method2", { "arg1": "arg1data" }, "c2" ], [ "method3", {}, "c3" ] ] }

3.4. The Response Object

A *Response* object has the following properties: o methodResponses: "Invocation[]" An array of responses, in the same format as the "methodCalls" on the Request object. The output of the methods MUST be added to the "methodResponses" array in the same order that the methods are processed. o createdIds: "Id[Id]" (optional; only returned if given in the request) A map of a (client-specified) creation id to the id the server assigned when a record was successfully created. This MUST include all creation ids passed in the original createdIds parameter of the Request object, as well as any additional ones added for newly created records. o sessionState: "String" The current value of the "state" string on the Session object, as described in Section 2. Clients may use this to detect if this object has changed and needs to be refetched. Unless otherwise specified, if the method call completed successfully, its response name is the same as the method name in the request.
Top   ToC   RFC8620 - Page 19

3.4.1. Example Response

{ "methodResponses": [ [ "method1", { "arg1": 3, "arg2": "foo" }, "c1" ], [ "method2", { "isBlah": true }, "c2" ], [ "anotherResponseFromMethod2", { "data": 10, "yetmoredata": "Hello" }, "c2"], [ "error", { "type":"unknownMethod" }, "c3" ] ], "sessionState": "75128aab4b1b" }

3.5. Omitting Arguments

An argument to a method may be specified to have a default value. If omitted by the client, the server MUST treat the method call the same as if the default value had been specified. Similarly, the server MAY omit any argument in a response that has the default value. Unless otherwise specified in a method description, null is the default value for any argument in a request or response where this is allowed by the type signature. Other arguments may only be omitted if an explicit default value is defined in the method description.

3.6. Errors

There are three different levels of granularity at which an error may be returned in JMAP. When an API request is made, the request as a whole may be rejected due to rate limiting, malformed JSON, request for an unknown capability, etc. In this case, the entire request is rejected with an appropriate HTTP error response code and an additional JSON body with more detail for the client. Provided the request itself is syntactically valid (the JSON is valid and when decoded, it matches the type signature of a Request object), the methods within it are executed sequentially by the server. Each
Top   ToC   RFC8620 - Page 20
   method may individually fail, for example, if invalid arguments are
   given or an unknown method name is called.

   Finally, methods that make changes to the server state often act upon
   a number of different records within a single call.  Each record
   change may be separately rejected with a SetError, as described in
   Section 5.3.

3.6.1. Request-Level Errors

When an HTTP error response is returned to the client, the server SHOULD return a JSON "problem details" object as the response body, as per [RFC7807]. The following problem types are defined: o "urn:ietf:params:jmap:error:unknownCapability" The client included a capability in the "using" property of the request that the server does not support. o "urn:ietf:params:jmap:error:notJSON" The content type of the request was not "application/json" or the request did not parse as I-JSON. o "urn:ietf:params:jmap:error:notRequest" The request parsed as JSON but did not match the type signature of the Request object. o "urn:ietf:params:jmap:error:limit" The request was not processed as it would have exceeded one of the request limits defined on the capability object, such as maxSizeRequest, maxCallsInRequest, or maxConcurrentRequests. A "limit" property MUST also be present on the "problem details" object, containing the name of the limit being applied. Example
{ "type": "urn:ietf:params:jmap:error:unknownCapability", "status": 400, "detail": "The Request object used capability '', which is not supported by this server." }
Top   ToC   RFC8620 - Page 21
   Another example:

       "type": "urn:ietf:params:jmap:error:limit",
       "limit": "maxSizeRequest",
       "status": 400,
       "detail": "The request is larger than the server is willing to

3.6.2. Method-Level Errors

If a method encounters an error, the appropriate "error" response MUST be inserted at the current point in the "methodResponses" array and, unless otherwise specified, further processing MUST NOT happen within that method call. Any further method calls in the request MUST then be processed as normal. Errors at the method level MUST NOT generate an HTTP-level error. An "error" response looks like this: [ "error", { "type": "unknownMethod" }, "call-id" ] The response name is "error", and it MUST have a type property. Other properties may be present with further information; these are detailed in the error type descriptions where appropriate. With the exception of when the "serverPartialFail" error is returned, the externally visible state of the server MUST NOT have changed if an error is returned at the method level. The following error types are defined, which may be returned for any method call where appropriate: "serverUnavailable": Some internal server resource was temporarily unavailable. Attempting the same operation later (perhaps after a backoff with a random factor) may succeed. "serverFail": An unexpected or unknown error occurred during the processing of the call. A "description" property should provide more details about the error. The method call made no changes to the server's state. Attempting the same operation again is expected to fail again. Contacting the service administrator is likely necessary to resolve this problem if it is persistent.
Top   ToC   RFC8620 - Page 22
   "serverPartialFail": Some, but not all, expected changes described by
   the method occurred.  The client MUST resynchronise impacted data to
   determine server state.  Use of this error is strongly discouraged.

   "unknownMethod": The server does not recognise this method name.

   "invalidArguments": One of the arguments is of the wrong type or is
   otherwise invalid, or a required argument is missing.  A
   "description" property MAY be present to help debug with an
   explanation of what the problem was.  This is a non-localised string,
   and it is not intended to be shown directly to end users.

   "invalidResultReference": The method used a result reference for one
   of its arguments (see Section 3.7), but this failed to resolve.

   "forbidden": The method and arguments are valid, but executing the
   method would violate an Access Control List (ACL) or other
   permissions policy.

   "accountNotFound": The accountId does not correspond to a valid

   "accountNotSupportedByMethod": The accountId given corresponds to a
   valid account, but the account does not support this method or data

   "accountReadOnly": This method modifies state, but the account is
   read-only (as returned on the corresponding Account object in the
   JMAP Session resource).

   Further possible errors for a particular method are specified in the
   method descriptions.

   Further general errors MAY be defined in future RFCs.  Should a
   client receive an error type it does not understand, it MUST treat it
   the same as the "serverFail" type.

3.7. References to Previous Method Results

To allow clients to make more efficient use of the network and avoid round trips, an argument to one method can be taken from the result of a previous method call in the same request. To do this, the client prefixes the argument name with "#" (an octothorpe). The value is a ResultReference object as described below. When processing a method call, the server MUST first check the arguments object for any names beginning with "#". If found, the result reference should be resolved and the value used as the "real"
Top   ToC   RFC8620 - Page 23
   argument.  The method is then processed as normal.  If any result
   reference fails to resolve, the whole method MUST be rejected with an
   "invalidResultReference" error.  If an arguments object contains the
   same argument name in normal and referenced form (e.g., "foo" and
   "#foo"), the method MUST return an "invalidArguments" error.

   A *ResultReference* object has the following properties:

   o  resultOf: "String"

      The method call id (see Section 3.2) of a previous method call in
      the current request.

   o  name: "String"

      The required name of a response to that method call.

   o  path: "String"

      A pointer into the arguments of the response selected via the name
      and resultOf properties.  This is a JSON Pointer [RFC6901], except
      it also allows the use of "*" to map through an array (see the
      description below).

   To resolve:

   1.  Find the first response with a method call id identical to the
       "resultOf" property of the ResultReference in the
       "methodResponses" array from previously processed method calls in
       the same request.  If none, evaluation fails.

   2.  If the response name is not identical to the "name" property of
       the ResultReference, evaluation fails.

   3.  Apply the "path" to the arguments object of the response (the
       second item in the response array) following the JSON Pointer
       algorithm [RFC6901], except with the following addition in
       "Evaluation" (see Section 4):

       If the currently referenced value is a JSON array, the reference
       token may be exactly the single character "*", making the new
       referenced value the result of applying the rest of the JSON
       Pointer tokens to every item in the array and returning the
       results in the same order in a new array.  If the result of
       applying the rest of the pointer tokens to each item was itself
       an array, the contents of this array are added to the output
       rather than the array itself (i.e., the result is flattened from
       an array of arrays to a single array).  If the result of applying
Top   ToC   RFC8620 - Page 24
       the rest of the pointer tokens to a value was itself an array,
       its items should be included individually in the output rather
       than including the array itself (i.e., the result is flattened
       from an array of arrays to a single array).

   As a simple example, suppose we have the following API request

                      [[ "Foo/changes", {
                          "accountId": "A1",
                          "sinceState": "abcdef"
                      }, "t0" ],
                      [ "Foo/get", {
                          "accountId": "A1",
                          "#ids": {
                              "resultOf": "t0",
                              "name": "Foo/changes",
                              "path": "/created"
                      }, "t1" ]]

   After executing the first method call, the "methodResponses" array

                      [[ "Foo/changes", {
                          "accountId": "A1",
                          "oldState": "abcdef",
                          "newState": "123456",
                          "hasMoreChanges": false,
                          "created": [ "f1", "f4" ],
                          "updated": [],
                          "destroyed": []
                      }, "t0" ]]

   To execute the "Foo/get" call, we look through the arguments and find
   there is one with a "#" prefix.  To resolve this, we apply the
   algorithm above:

   1.  Find the first response with method call id "t0".  The "Foo/
       changes" response fulfils this criterion.

   2.  Check that the response name is the same as in the result
       reference.  It is, so this is fine.

   3.  Apply the "path" as a JSON Pointer to the arguments object.  This
       simply selects the "created" property, so the result of
       evaluating is: [ "f1", "f4" ].
Top   ToC   RFC8620 - Page 25
   The JMAP server now continues to process the "Foo/get" call as though
   the arguments were:

                             "accountId": "A1",
                             "ids": [ "f1", "f4" ]

   Now, a more complicated example using the JMAP Mail data model: fetch
   the "from"/"date"/"subject" for every Email in the first 10 Threads
   in the inbox (sorted newest first):

      [[ "Email/query", {
        "accountId": "A1",
        "filter": { "inMailbox": "id_of_inbox" },
        "sort": [{ "property": "receivedAt", "isAscending": false }],
        "collapseThreads": true,
        "position": 0,
        "limit": 10,
        "calculateTotal": true
      }, "t0" ],
      [ "Email/get", {
        "accountId": "A1",
        "#ids": {
          "resultOf": "t0",
          "name": "Email/query",
          "path": "/ids"
        "properties": [ "threadId" ]
      }, "t1" ],
      [ "Thread/get", {
        "accountId": "A1",
        "#ids": {
          "resultOf": "t1",
          "name": "Email/get",
          "path": "/list/*/threadId"
      }, "t2" ],
      [ "Email/get", {
        "accountId": "A1",
        "#ids": {
          "resultOf": "t2",
          "name": "Thread/get",
          "path": "/list/*/emailIds"
        "properties": [ "from", "receivedAt", "subject" ]
      }, "t3" ]]
Top   ToC   RFC8620 - Page 26
   After executing the first 3 method calls, the "methodResponses" array
   might be:

       [[ "Email/query", {
           "accountId": "A1",
           "queryState": "abcdefg",
           "canCalculateChanges": true,
           "position": 0,
           "total": 101,
           "ids": [ "msg1023", "msg223", "msg110", "msg93", "msg91",
               "msg38", "msg36", "msg33", "msg11", "msg1" ]
       }, "t0" ],
       [ "Email/get", {
           "accountId": "A1",
           "state": "123456",
           "list": [{
               "id": "msg1023",
               "threadId": "trd194"
           }, {
               "id": "msg223",
               "threadId": "trd114"
           "notFound": []
       }, "t1" ],
       [ "Thread/get", {
           "accountId": "A1",
           "state": "123456",
           "list": [{
               "id": "trd194",
               "emailIds": [ "msg1020", "msg1021", "msg1023" ]
           }, {
               "id": "trd114",
               "emailIds": [ "msg201", "msg223" ]
           "notFound": []
       }, "t2" ]]

   To execute the final "Email/get" call, we look through the arguments
   and find there is one with a "#" prefix.  To resolve this, we apply
   the algorithm:

   1.  Find the first response with method call id "t2".  The "Thread/
       get" response fulfils this criterion.
Top   ToC   RFC8620 - Page 27
   2.  "Thread/get" is the name specified in the result reference, so
       this is fine.

   3.  Apply the "path" as a JSON Pointer to the arguments object.
       Token by token:

       1.  "list": get the array of thread objects

       2.  "*": for each of the items in the array:

           a.  "emailIds": get the array of Email ids

           b.  Concatenate these into a single array of all the ids in
               the result.

   The JMAP server now continues to process the "Email/get" call as
   though the arguments were:

    "accountId": "A1",
    "ids": [ "msg1020", "msg1021", "msg1023", "msg201", "msg223", ... ],
    "properties": [ "from", "receivedAt", "subject" ]

   The ResultReference performs a similar role to that of the creation
   id, in that it allows a chained method call to refer to information
   not available when the request is generated.  However, they are
   different things and not interchangeable; the only commonality is the
   octothorpe used to indicate them.

3.8. Localisation of User-Visible Strings

If returning a custom string to be displayed to the user, for example, an error message, the server SHOULD use information from the Accept-Language header of the request (as defined in Section 5.3.5 of [RFC7231]) to choose the best available localisation. The Content- Language header of the response (see Section of [RFC7231]) SHOULD indicate the language being used for user-visible strings. For example, suppose a request was made with the following header: Accept-Language: fr-CH, fr;q=0.9, de;q=0.8, en;q=0.7, *;q=0.5 and a method generated an error to display to the user. The server has translations of the error message in English and German. Looking at the Accept-Language header, the user's preferred language is French. Since we don't have a translation for this, we look at the
Top   ToC   RFC8620 - Page 28
   next most preferred, which is German.  We have a German translation,
   so the server returns this and indicates the language chosen in a
   Content-Language header like so:

                           Content-Language: de

3.9. Security

As always, the server must be strict about data received from the client. Arguments need to be checked for validity; a malicious user could attempt to find an exploit through the API. In case of invalid arguments (unknown/insufficient/wrong type for data, etc.), the method MUST return an "invalidArguments" error and terminate.

3.10. Concurrency

Method calls within a single request MUST be executed in order. However, method calls from different concurrent API requests may be interleaved. This means that the data on the server may change between two method calls within a single API request.

4. The Core/echo Method

The "Core/echo" method returns exactly the same arguments as it is given. It is useful for testing if you have a valid authenticated connection to a JMAP API endpoint.

4.1. Example

Request: [[ "Core/echo", { "hello": true, "high": 5 }, "b3ff" ]] Response: [[ "Core/echo", { "hello": true, "high": 5 }, "b3ff" ]]

(next page on part 3)

Next Section