Reference

FlexServices is a framework for developing low code, lightweight microservices that are used for data integrations, functional business logic, and integration with custom authentication providers. FlexServices utilizes the Flex SDK.

Contents:

FlexData

FlexData Routes

FlexData provides routes for CRUD and count operations. In your FlexService, you need to implement an event handler for each endpoint that you plan to use. See FlexData: Data Events for details.

EndpointHTTP VerbDescriptionFlexData Event
/:serviceObject/POSTCreates a service object entityonInsert
/:serviceObject/:idPUTUpdates a service object entityonUpdate
/:serviceObject/:idDELETEDeletes a single service object entity by IDonDeleteById
/:serviceObject/?{query}DELETEDeletes all service object entities that match a queryonDeleteByQuery
/:serviceObject/DELETEDeletes all entities of a service objectonDeleteAll
/:serviceObject/GETGets all entities in a service objectonGetAll
/:serviceObject/:idGETGets a single service object entity by IDonGetById
/:serviceObject/?{query}GETGets all service object entities that match a queryonGetByQuery
/:serviceObject/_countGETGets the count of the entities in a service objectonGetCount
/:serviceObject/_count/?{query}GETGets the count of the entities in a query resultonGetCountByQuery

FlexData Methods

MethodDescription
flex.data.serviceObject(serviceObjectName)Creates a service object

flex.data.serviceObject(serviceObjectName)

Creates a service object that represents a data object or record on the remote system that you connect to using FlexData. Examples include an SQL table or view, a Sharepoint list, or an SAP RFC.

NameTypeDefault ValueDescription
serviceObjectNameString-Name for the service object to be created

Examples

// To register the "widgets" ServiceObject:
const widgets = flex.data.serviceObject('widgets');

FlexData Events

Each ServiceObject exposes data events that are invoked by Kinvey collections. The data event takes a single data handler function to be executed.

FlexData EventDescriptionEndpointHTTP Verb
onInsert(callback)Creates a service object entity/:serviceObject/POST
onUpdate(callback)Updates a service object entity/:serviceObject/:idPUT
onDeleteById(callback)Deletes a single service object entity by ID/:serviceObject/:idDELETE
onDeleteByQuery(callback)Deletes all service object entities that match a query/:serviceObject/?{query}DELETE
onDeleteAll(callback)Deletes all entities of a service object/:serviceObject/DELETE
onGetAll(callback)Gets all entities in a service object/:serviceObject/GET
onGetById(callback)Gets a single service object entity by ID/:serviceObject/:idGET
onGetByQuery(callback)Gets all service object entities that match a query/:serviceObject/?{query}GET
onGetCount(callback)Gets the count of the entities in a service object/:serviceObject/_countGET
onGetCountByQuery(callback)Gets the count of the entities in a query result/:serviceObject/_count/?{query}GET

Examples

widgets.onGetAll(callbackFunction);

Data Handler Function

A function to be executed when a particular FlexData event takes place.

callback(context, complete, modules);
ArgumentTypeDescription
contextObjectRepresents the current context of the FlexData request made to Kinvey
completeObjectCompletion handler for completing the data request
modulesObjectContains libraries for accessing Kinvey functionality

context Object

Represents the current context of the FlexData request made to Kinvey.

PropertyDescription
methodThe HTTP method (GET, POST, PUT, DELETE)
headersThe HTTP request headers
entityIdThe Entity ID, if specified
serviceObjectNameThe name of the Service Object
bodyThe HTTP body
queryAn object containing all query string parameters

complete Object

Completion handler for completing the data request. It follows a builder pattern for creating the handler's response.

complete()[.setBody(<entity>).<status>.done()|next()]

Example:

complete(myEntity).ok().next();
complete() Function

Initiates the completion process.

setBody() Function

An optional context-altering method that sets the response body. It takes an entity, an array of entities, an error description, or an Error object. The entity must be a JSON object or a JSON array.

Examples:

// Sets the response to include an entity.
complete().setBody({'foo':'bar'});

// Sets the response to include an array of entities
complete().setBody([{'foo':'bar'}, {'abc':'123'}]);

// Sets the response to an empty object
complete();

// Sets the response to an empty array
complete().setBody([]);

// Sets the response to an error string to be used with error status functions
complete().setBody('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setBody(new Error('Record 123 was not found');
Status Function

Sets the status code for a Flex Data operation. Automatically sets the response body to a Kinvey-formatted error and uses the value passed into the status function or the setData() function as the debug property, if it is present. Optional.

FunctionStatus CodeDescription
ok200Normal success response
created201New record has been created
accepted202Request has been submitted for processing, but will be processed asynchronously
notFound404Entity or entities could not be found
badRequest400Invalid request
unauthorized401Request is not authorized for the given user context
forbidden403Request is forbidden for some reason
notAllowed405Request or request method is not allowed
notImplemented501Handler is not implemented
runtimeError550Use for custom runtime errors

Examples:

// Return that the record has been created
complete().setBody(myRecord).created();

// Entity wasn't found
complete().setBody("The given entity wasn't found").notFound();
End Processing Function

End the processing of the handler request after setting the response status. Optional.

FunctionDescription
next()Ends processing and continues the Kinvey request pipeline
done()Returns the response set in the completion handler and ends request processing without executing any further functions.

Examples:

// Continue the request chain
complete().setBody(myEntity).ok().next();

// End the request chain with no further processing
complete().ok().done();

modules Object

An object containing instances of all available modules.

Usage:

modules.[moduleName].[moduleMethodName()];

Examples:

// data handler
function onGetById(context, complete, modules) {
  const appKey = modules.backendContext.getAppKey();
  // ...
}

FlexFunctions

The FlexFunctions framework is used to execute RPC functions invoked by hooks or endpoints. You can access it through the SDK's functions property.

const functions = flex.functions;

FlexFunctions Routes

EndpointHTTP VerbDescription
/_flexFunctions/:handlerNamePOSTExecutes the handler function

FlexFunctions Methods

MethodDescription
flex.functions.register(eventHandlerName, callback)Registers a Flex function handler

flex.functions.register(eventHandlerName, callback)

Registers a Flex function handler. This includes naming the handler and defining its logic.

NameTypeDefault ValueDescription
eventHandlerNameString-Name for the Flex function handler.
callbackFunction-Function that defines the behavior of the handler

Examples

const sdk = require('kinvey-flex-sdk');
const request = require('request'); // assumes that the request module was added to package.json
sdk.service(function(err, flex) {

  const flexFunctions = flex.functions;   // gets the FlexFunctions object from the service

  function getRedLineSchedule(context, complete, modules) {
    request.get('http://developer.mbta.com/Data/Red.json', (err, response, body) => {
      // if error, return an error
      if (err) {
        return complete().setBody('Could not complete request').runtimeError().done();
      }

      //otherwise, return the results
      return complete().setBody(body).ok().done();
    });

   }

  // set the handler
  flexFunctions.register('getRedLineData', getRedLineSchedule);
};

Handler Function

A function to be executed when a particular Flex function is called.

callback(context, complete, modules);
ArgumentTypeDescription
contextObjectRepresents the current context of the request to Kinvey
completeObjectCompletion handler for completing the function
modulesObjectContains libraries for accessing Kinvey functionality

context Object

Represents the current context of the request to Kinvey. Some of the object properties are set only at the appropriate stages of the Kinvey request pipeline.

PropertyDescription
methodThe HTTP method (GET, POST, PUT, DELETE)
headersThe HTTP request headers
entityIdWhen the Flex function is used as a collection hook, this is the Entity ID included in the request, if specified.
bodyWhen the Flex function is used as a collection hook, this is a data entity or entities, or error message associated with the request. When the Flex function is used as a custom endpoint, this is the request body.
queryAn object containing all query string parameters
hookTypeThe type of event hook: customEndpoint (default) for an endpoint hook, pre for a Before collection hook, post for an After collection hook
userIdID of the user making the request
usernameUsername of the user making the request
objectNameFor Before and After collection hooks, this is the name of the collection. For custom endpoints, its value is undefined.

complete Object

Completion handler for completing the function. It follows a builder pattern for creating the handler's response.

complete().[setBody(<entity>).setQuery(<query>).<status>.done()|next()]

Example:

complete(myEntity).ok().next();
complete() Function

Initiates the completion process.

setBody() Function

An optional context-altering method that sets the data entity or entities to be passed to the next step of the pipeline or as the final result. It takes an entity, an array of entities, an error description, or an Error object. The entity must be a JSON object or a JSON array.

Examples:

// Sets the context to include an entity
complete().setBody({'foo':'bar'});

// Sets the response to include an array of entities
complete().setBody([{'foo':'bar'}, {'abc':'123'}]);

// Sets the response to an empty object
complete();

// Sets the response to an empty array
complete().setBody([]);

// Sets the response to an error string to be used with error status functions
complete().setBody('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setBody(new Error('Record 123 was not found');
setQuery() Function

Replaces the query object with an altered query object. This is only useful in Before collection hook functions. Optional.

Examples:

// Sets the context to include an entity and an altered query
complete().setBody({'foo':'bar'}).setQuery({query: {foo:'bar'}});
Status Function

Sets the status code for a FlexFunctions operation. Automatically sets the response body to a Kinvey-formatted error and uses the value passed into the status function or the setBody() function as the debug property, if it is present. Optional.

FunctionStatus CodeDescription
ok200Normal success response
created201New record has been created
accepted202Request has been submitted for processing, but will be processed asynchronously
notFound404Entity or entities could not be found
badRequest400Invalid request
unauthorized401Request is not authorized for the given user context
forbidden403Request is forbidden for some reason
notAllowed405Request or request method is not allowed
notImplemented501Handler is not implemented
runtimeError550Use for custom runtime errors

Examples:

// Return that the record has been created
complete().setBody(myRecord).created();

// Entity wasn't found
complete().setBody("The given entity wasn't found").notFound();
End Processing Function

End the processing of the handler request after setting the response status. Optional.

FunctionDescription
next()Ends processing and continues the Kinvey request pipeline
done()Returns the response set in the completion handler and ends request processing without executing any further functions.

Examples:

// Continue the request chain
complete().setBody(myEntity).ok().next();

// End the request chain with no further processing
complete().ok().done();

modules Object

An object containing instances of all available modules.

Usage:

modules.[moduleName].[moduleMethodName()];

Examples:

flex.functions.register('processPayment', function processPaymentCallback(context, complete, modules) {
  const appKey = modules.backendContext.getAppKey();
});

FlexAuth

FlexAuth Routes

EndpointHTTP VerbDescription
/_auth/:handlerNamePOSTExecutes the handler function

FlexAuth Methods

MethodDescription
flex.auth.register(authHandlerName, callback)Registers a FlexAuth handler

flex.auth.register(authHandlerName, callback)

Registers a FlexAuth handler. This includes naming the handler and defining its logic.

NameTypeDefault ValueDescription
authHandlerNameString-Name for the FlexAuth handler.
callbackFunction-Function that defines the behavior of the handler

Examples

const sdk = require('kinvey-flex-sdk');
const request = require('request'); // assume that the request module was added to package.json
sdk.service(function(err, flex) {

  const flexAuth = flex.auth;   // get the FlexAuth object from the service

  function authenticate(context, complete, modules) {
    // authenticate the user here
    if (err) {
      return complete().accessDenied(err).next();
    }
    return complete().setToken(token).ok().next();
  }

  // set the handler
  flexAuth.register('myAuth', authenticate);
};

Handler Function

A function to be executed when a particular FlexAuth handler is called.

callback(context, complete, modules);
ArgumentTypeDescription
contextObjectRepresents the current context of the request to Kinvey
completeObjectCompletion handler for completing the function
modulesObjectContains libraries for accessing Kinvey functionality

context Object

Represents the current context of the request to Kinvey. Some of the object properties are set only at the appropriate stages of the Kinvey request pipeline.

PropertyDescription
methodThe HTTP method (GET, POST, PUT, DELETE)
headersThe HTTP request headers
bodyThe request body
body.usernameUsername of the user making the request
body.passwordPassword of the user making the request
body.optionsOptional request parameters

complete Object

Completion handler for completing the handler. It follows a builder pattern for creating the handler's response.

complete().[setToken(<token>).addAttribute(<key>, <value>).removeAttribute(<key>).<status>.done()|next()]

Example:

complete().setToken(myToken).addAttribute('userEmail', myEmail).ok().next()
complete() Function

Initiates the completion process.

setToken() Function

Sets the authentication token to be used for this user. Must be a JSON object or a Base64 encoded string. For errors, you can either pass a string as the error message or a JavaScript Error object. Optional.

Examples:

// Set an access token as a JSON object
complete().setToken({'myAuthToken': 'ffds9afdsafdsaf89ds0fds90f8ds-='});

// Set the response to an error string to be used with error status functions
complete().setToken('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setToken(new Error('Record 123 was not found');
addAttribute() Function

Adds custom attributes to your FlexAuth response. Optional.

Examples:

complete().setToken({'myAuthToken': 'ffds9afdsafdsaf89ds0fds90f8ds-='}).addAttribute('email', 'test123@test.com');
removeAttribute() Function

Removes a previously added custom attribute from your FlexAuth response. Optional.

Examples:

complete().setToken({'myAuthToken': 'ffds9afdsafdsaf89ds0fds90f8ds-='}).removeAttribute('email');
Status Function

Sets the status code for a FlexAuth operation. Also sets the body to an OAuth-formatted error and uses the value passed into the status function as the debug property, if it is present.

FunctionStatus CodeDescription
ok200Normal success response
serverError401For arbitrary server errors.
accessDenied401Thee authenticating user has been denied access
temporarilyUnavailable401The underlying authentication source is temporarily unavailable
notImplemented401Handler is not implemented

Examples:

// Return that the user has been authenticated
complete().setToken(myToken).ok();
End Processing Function

End the processing of the handler request after setting the response status. Optional.

FunctionDescription
next()Ends processing and continues the Kinvey request pipeline
done()Returns the response set in the completion handler and ends request processing without executing any further functions.
// Continue up the request chain
complete().setToken(myToken).ok().next();

// End the request chain with no further processing
complete().ok().done();

modules Object

An object containing instances of all available modules.

Usage:

modules.[moduleName].[moduleMethodName()];

Examples:

flex.auth.register('myAuth', function myAuthCallback(context, complete, modules) {
  const appKey = modules.backendContext.getAppKey();
});

Service Discovery

Service Discovery Routes

EndpointHTTP VerbDescription
/_command/discoverPOSTReturns a JSON object that lists all registered Flex objects on your Kinvey account. This includes FlexData service objects, FlexFunctions, and FlexAuth endpoints.

Result format

Field nameTypeDescription
dataLinkobjectRepresents FlexData
dataLink.serviceObjectsarrayLists all registered FlexData service objects
businessLogicobjectRepresents FlexFunctions
businessLogic.handlersarrayLists all registered FlexFunctions handler functions
authobjectRepresents FlexAuth
auth.handlersarrayLists all registered FlexAuth handler functions

Examples

{
    "dataLink": {
        "serviceObjects": [
            "musicCatalog"
        ]
    },
    "businessLogic": {
        "handlers": [
            "checkIfUpsert",
            "updateMultipleItems",
            "bulkUpload",
            "deleteUsers",
            "getDataWithQuery",
            "printFlexContext",
            "sendEmail",
            "deleteOldItems"
        ]
    },
    "auth": {
        "handlers": ["MyAuth"]
    }
}

Modules

The Flex SDK comes with the following modules pre-installed:

Module nameDescription
backendContextMethods to access information about the current backend context
dataStoreFetch, query, and write to Kinvey collections
emailSend email notifications
groupStoreFetch, query, and write to Kinvey groups
Kinvey EntityUtilities for working with Kinvey entities.
kinveyDateUtilities to convert dates to/from the Kinvey date format.
pushMethods for sending push notifications to a user's device.
QueryCreate queries to be used by the dataStore module.
requestContextMethods to access information about the current request context.
tempObjectStoreKey-value store for persisting temporary data between a pre- and a post-hook.
userStoreFetch, query, create, update, delete, suspend, and restore Kinvey users.

backendContext Module

Used to return data and perform functions against the current backend context.

MethodBehavior
getAppKey()Returns the current backend's App Key
getAppSecret()Returns the current backend's App Secret
getMasterSecret()Returns the current backend's Master Secret

Examples

One common use case for these functions is for calling requests against the current backend to provide a method to obtain the correct authentication credentials. This allows for BL scripts to be migrated from backend to backend without code changes, such as in the case of seperate development and production backends.

const request = require('request');

function myHandler(context, complete, modules){

  const backendContext = modules.backendContext;

  const appKey = backendContext.getAppKey();
  const masterSecret = backendContext.getMasterSecret();

  const uri = 'https://' + context.headers.host + '/appdata/' + appKey + '/myCollection/';
  const authString = 'Basic ' + Buffer.from(appKey + ':' + masterSecret).toString('base64');
  const requestOptions = {
    uri:uri,
    headers: {
      'Authorization':authString
    }
  };

  var auth = request.get(requestOptions, (error, res, body) => {
    if (error){
      complete.setBody(error).runtimeError.done();
    } else {
      complete.setBody(body).ok().next();
    }
  });
}

dataStore Module

Use the dataStore module to interact with Kinvey Data at the collection level. The dataStore module can be used to interact with both the Kinvey DataStore or with external data such as RAPID connectors or Flex Data Services.

To initialize the DataStore:

const store = modules.dataStore();

The dataStore method also takes an optional options argument is an optional object containing store options for the current store. The options are:

OptionDescription
useBlIf set to true, executes BL hooks associated with the dataStore request. If false, business logic hooks will not execute. Defaults to false.
useUserContextUses user context credentials to access the datastore if set to true. If false, will execute in the context of mastersecret. Defaults to false.
useMasterSecretDEPRECATED: use useUserContext instead Uses the mastersecret credentials to access the datastore if set to true. If set to false, will use the current user credentials.
skipBlDEPRECATED: use useBl instead If set to true, skips BL processing when accessing the datastore. If false or not included, it will default to executing any BL associated with the store.

Requests that use userContext will automatically execute BL, as requests to the Kinvey platform using a user context cannot skip BL.

For example:

const options = {
  useBl: true,
  useUserContext: false
}

const store = modules.dataStore(options);

The dataStore object contains a single method, collection for specifying the collection to query.

const myCollection = modules.dataStore().collection('myCollection');

The collection object contains methods for accessing data within the collection. All methods take a callback(err, results) function.

MethodDescription
find(query, callback)Finds the records contained in the query. The query is a Query object created with modules.Query. If no Query object is supplied, the find method will return all entities in the collection.
findById(entityId, callback)Finds a single entity by its _id.
save(entity, callback)Saves the provided entity to the dataStore, either as an insert or update. (NOTE: if an _id is provided and that _id already exists, the save will update the existing entity by replacing it with the entity passed into this function).
remove(query, callback)Removes the records contained in the query. The query is a Query object created with modules.Query.
removeById(entityId, callback)Removes a single entity by its _id.
count(query, callback)Gets a count of all records that would be returned by the query. The query is a Query object created with modules.Query. If no Query object is supplied, the count method will return a count of the number of entities in the collection.

Circular requests (request to the same collection as the originating Flex request) must be executed under mastersecret credentials and must not use business logic. If either useUserContext or useBl are set to true, these types of requests will fail with an error in the callback.

For example:

  const store = dataStore({ useUserContext: true });
  const products = store.collection('products');
  products.findById(1234, (err, result) => {
    if (err) {
      return complete(err).runtimeError().done();
    }
    result.status = 'Approved';
    products.save(result, (err, savedResult) => {
      if (err) {
        return complete().setBody(err).runtimeError().done();
      }
      complete().setBody(savedResult).ok().next();
    });
  });
});

When testing dataStore, locally in particular, special headers need to be added to your local tests. These headers will be added automatically by Kinvey in production use. For information on the required headers, see the section on testing locally

email Module

Use the email module to send email to any valid email address.

Available Methods:

MethodAction
send(from, to, subject, text_body, reply_to, html_body, cc, bcc, callback)Send an email to to from from using the subject and body. reply_to is an optional argument for the reply to line of the message. For the email body, text_body is for the plain-text version of the email and is required. html_body is an optional parameter that accepts an HTML-formatted string, which creates an optional html body for the email message. The cc and bcc arguments for adding CC and BCC users are optional.

For example, to send a text email:

  const email = modules.email;
  email.send('my-app@my-app.com',
         request.body.friendEmail,
         'Join my octagon in my-app!',
         'You\'ve been invited to join ' + request.body.name + '\'s octagon in my-app!', function(err, result) {
         complete().ok().next();
         });

To send an HTML email, it is important to note that the reply-to must be included; if you want the reply-to to be the same as the from address, you can either also pass the from address in the reply_to argument, or simply pass a null value (which will default the reply to the from address). For example:

  const email = modules.email;
  email.send('my-app@my-app.com',
           request.body.friendEmail,
           'Join my octacgon in my-app!',
           'You\'ve been invited to join ' + request.body.name + '\'s octagon in my-app!',
           null,
           '<html>You\'ve been invited to join  <b>' + request.body.first_name + '</b>\'s octagon in my-app!</html>', callback);

Email calls are asynchronous in nature. They can be invoked without a callback, but are not guaranteed to complete before continuing to the next statement. However, once invoked they will complete the sending of the email, even if the function ends via a complete handler.

The email module is currently only available for services running on the Kinvey Microservices Runtime, and is not available externally or for local testing.

groupStore Module

Use the groupStore module to interact with Kinvey Groups. The groupStore module can be used to create, update, retrieve, and remove Kinvey User groups.

To initialize the groupStore:

const store = modules.groupStore();

The groupStore method also takes an optional options argument is an optional object containing store options for the current store. The options are:

OptionDescription
useUserContextUses user context credentials to access the groupStore if set to true. If false, will execute in the context of mastersecret. Defaults to false.

For example:

const options = {
  useUserContext: true
}

const store = modules.groupStore(options);

The groupStore object contains methods for accessing Kinvey groups. All methods take a callback function.

Most methods take a callback with err and results arguments.

MethodDescription
create(group, callback)Creates a new group. The group object should be formatted according to the Group API Specification
update(ugroupser, callback)Updates the provided group. Note that the group entity supplied must contain an _id. The group object should be formatted according to the Group API Specification
remove(groupId, callback)Removes a single group by its _id. Note this method deletes the user from the userStore and is non-recoverable.
findById(groupId, callback)Finds a user based on its ID.

For example:

  const store =  groupStore({ useUserContext: true });
  store.findById(1234, (err, result) => {
    if (err) {
      return complete().setBody(err).runtimeError().done();
    }
    store.update(result, (err, savedResult) => {
      if (err) {
        return complete(err).runtimeError().done();
      }
      complete().setBody(savedResult).ok().next();
    });
  });

When testing groupStore, locally in particular, special headers need to be added to your local tests. These headers will be added automatically by Kinvey in production use. For information on the required headers, see the section on testing locally.

kinveyEntity Module

A Kinvey Entity is a JSON object containing Kinvey-specific metadata for use with AppData collections. Kinvey Entities also contain methods to manipulate Kinvey metadata.

The kinveyEntity module provides the following methods:

MethodBehavior
entity()Returns a Kinvey Entity with system generated _id.
entity(id)Returns a Kinvey Entity with the supplied id as the _id property.
entity(JsonObject)Updates JsonObject with Kinvey-specific metadata and functions, and returns a Kinvey Entity as the result.
isKinveyEntity(someObject)Returns true if someObject is a Kinvey Entity returned from one of the entity() methods above.

For example, to create a Kinvey Entity with only Kinvey metadata:

function someHandler(context, complete, modules) {
  complete().setBody(modules.kinveyEntity.entity()).ok().done();
}

The response would contain an entity containing no additional attributes, except for Kinvey metadata attributes:

{
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "lmt": "2013-05-08T13:48:36+00:00",
    "ect": "2013-05-08T13:48:36+00:00"
  },
  "_id": "518a57b4d79b4d4308000002"
}

To use your own _id, you can simply pass that ID as a String into the method:

function handler(context, complete, modules){
  var myID = '000-22-2343';
  complete().setBody(modules.kinvey.entity(myID)).ok().done();
}
{
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "lmt": "2013-05-08T13:51:02+00:00",
    "ect": "2013-05-08T13:51:02+00:00"
  },
  "_id": "000-22-2343"
}

You can add Kinvey metadata to your own JavaScript object by passing that object into the function.

function handler(context, complete, modules){
  function employee() {
    this.firstName = 'John';
    this.lastName = 'Doe';
    this.status = 'Active';
    this.dob = '5/5/1985';
    this.doh = '1/12/2012';
  }

  complete().setBody(modules.kinvey.entity(new employee())).ok().done();
}

This example results in the Kinvey metadata being added to your employee object.

{
  "firstName": "John",
  "lastName": "Doe",
  "status": "Active",
  "dob": "5/5/1985",
  "doh": "1/12/2012",
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "etc": "2013-05-08T13:57:11+00:00",
    "lmt": "2013-05-08T13:57:11+00:00"
  },
  "_id": "518a59b7d79b4d4308000003"
}

Note that you can add properties directly to a Kinvey Entity and pass it directly into a dataStore method that takes an entity as an argument:

function handler(context, complete, modules) {
  const entity = modules.kinveyEntity.entity();
  entity.name = 'Sachin';
  modules.dataStore().collection('People').save(entity, (err, result) =>
    if (err) {
      // handle error
    } else {
      complete().setBody(result).ok().next();
    }
  });
}

If you call entity(JsonObject) with an object that already contains Kinvey metadata, the original metadata will be maintained. Only lmt will be updated with the current date and time.

_acl Namespace

Kinvey entities returned by entity() include an _acl namespace containing methods for accessing that entity's user, role, and group access privileges:

MethodBehavior
_acl.getCreator()Return entity creator user ID as a String
_acl.getReaders()Return Array of String containing user IDs with read access
_acl.getWriters()Return Array of String containing user IDs with write access
_acl.getReaderGroups()Return Array of String containing group IDs with read access
_acl.getWriterGroups()Return Array of String containing group IDs with write access
_acl.getReaderRoles()Return Array of String containing role IDs with read access
_acl.getUpdateRoles()Return Array of String containing role IDs with update access
_acl.getDeleteRoles()Return Array of String containing role IDs with delete access
_acl.addReader(userId)Grant read access to userId, passed as String. Returns the _acl namespace for method chaining.
_acl.addWriter(userId)Grant write access to userId, passed as String. Returns the _acl namespace for method chaining.
_acl.addReaderGroup(groupId)Grant read access to groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.addWriterGroup(groupId)Grant write access to groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.addReaderRole(roleId)Grant read access to roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.addUpdateRole(roleId)Grant update access to roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.addDeleteRole(roleId)Grant delete access to roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeReader(userId)Revoke read access from userId , passed as String. Returns the _acl namespace for method chaining. (userId will retain read access if part of a group with read access)
_acl.removeWriter(userId)Revoke write access from userId , passed as String. Returns the _acl namespace for method chaining. (userId will retain write access if part of a group with write access)
_acl.removeReaderGroup(groupId)Revoke read access from groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeWriterGroup(groupId)Revoke write access from groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeReaderRole(roleId)Revoke read access from roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeUpdateRole(roleId)Revoke update access from roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeDeleteRole(roleId)Revoke delete access from roleId, passed as String. Returns the _acl namespace for method chaining.
_acl.getGloballyReadable()Return bool indicating whether entity is readable by all users
_acl.getGloballyWritable()Return bool indicating whether entity is writable by all users
_acl.setGloballyReadable(gr)Set global readability to gr, passed as bool. Returns the _acl namespace for method chaining.
_acl.setGloballyWritable(gw)Set global writability to gw, passed as bool. Returns the _acl namespace for method chaining.
ACL method chaining

All _acl methods can be chained to the modules.kinveyEntity.entity() by chaining the _acl namespace followed by the desired method. Furthermore the _acl modifier methods (those that return the _acl namespace) can be chained to each other. Since the _acl modifier methods return the _acl namespace, there's no need to restate the _acl namespace for the next method in the chain.

For example, the following line will create a Kinvey Entity, add a reader, add a writer, then return all readers.

Because getReaders() returns an array, no futher _acl methods can be chained to it.

modules.kinveyEntity.entity(someObject)._acl.addReader(readUserId).addWriter(writeUserId).getReaders();
Example

The example below uses kinveyEntity.entity()._acl along with the Request Context module to grant write access on the requested entity to the user making the request:

function handler(context, complete, modules) {
  const currentUser = modules.requestContext.getAuthenticatedUserId();
  const entityId = context.entityId;

  const collection = modules.dataStore().collection(collectionName);

  collection.findById({entityId, (err, doc) => {
      if (err){
        //handle error
      } else if (doc) {

        const entity = modules.kinveyEntity.entity(doc)._acl.addWriter(currentUser);
        //Note we directly pass the Kinvey Entity to the save method.
        collection.save(entity, (err, result) => {
          complete().setBody(result).ok().next();
        });
      } else {
                  // entity not found
                  complete().setBody(new Error 'Entity not found').runtimeError().done();
             }
    }
  });
}

kinveyDate Module

A utility for converting dates.

MethodBehavior
toKinveyDateString(date)Converts a Date object, a moment, or a date string into the Kinvey date format. If the passed in date is not a valid date, "Invalid date" will be returned.
fromKinveyDateString(str, format)Decodes a Kinvey date string into the specified format. Valid formats are date, moment and string. If no format is provided, a JavaScript Date object is returned. If the passed in str is not a valid date, "Invalid date" will be returned.

For example, to convert a date to a Kinvey Date string:

myDate = new Date();
kinveyDate = modules.kinveyDate.toKinveyDateString(myDate);

To convert a Kinvey Date string to a JavaScript date object:

myDate = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'date');
// note:  this could also be accomplished via modules.kinveyDate.fromKinveyDateString(kinveyDate);

To convert a Kinvey Date string to a moment:

myMoment = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'moment');

To convert a Kinvey Date into a standard ISO-8601 date string

myDateString = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'string');

push Module

Use the push module to send a push notification to a device registered with your app.

Available Methods:

MethodAction
broadcastMessage(message, calback)Broadcasts a simple text-only message to all devices registered with this app
sendMessage(users, message, callback)Sends a simple text-only message to the users in users
send(users, message, callback)Alias for sendMessage()
sendPayload(users, iOSAps, iOSExtras, androidPayload, callback)Sends a custom payload to the users in users which allows for sending advanced options and data
broadcastPayload(iOSAps, iOSExtras, androidPayload, callback)Broadcasts custom payloads to all devices registered with this app which allows for sending advanced options and data

For example, to send a broadcast message:

  var push = modules.push;
  if (context.body.sendMessageToAll){
    push.broadcastMessage(context.body.message, (err, result) => {
      complete().ok().next();
    });
  }

The users argument to send is either an array of Kinvey User documents or a single user document. The message will be sent to all iOS or Android devices that are contained in the documents in users.

You can query the user collection with the dataStore module to get a list of users.

For example:

  const push = modules.push;
  const userStore = modules.userStore();
  const query = new modules.Query();

  query.equalTo('firstName', request.body.firstName);

  userStore.find(query, (err, userColl) => {
    if (err) {
    complete().setBody(err).runtimeError().done();
    } else {
    userColl.forEach((user) => {
      push.sendMessage(user, 'People who are named ${user.firstName} are awesome!', (err, result) => {
      //complete here
      });
    });
    }
  });
Payloads

The methods sendPayload and broadcastPayload allow for more robust push messages. For iOS, the payload consists of two arguments, iOSAps and iOSExtras. The former is a JSON object that can have the following properties:

FieldValue
alertAn alert message to display to the user
badgeThe badge number to appear on the notification
soundThe name of the sound file in the application package to play
content-availableIf set to 1, the remote notification acts as a "silent" notification

The iOSExtras argument is a JSON object with custom name-value pairs to be included in the push notification.

For Android, the payload is contained in the androidPayload attribute, which is any JSON object to be sent to Google Cloud Messaging (GCM).

Each of the arguments above are null-safe.

For example, to send a broadcast with payloads:

const iOSAps = { alert: 'You have a new message', badge: 2, sound: 'notification.wav' }
const iOSExtras = {from: 'Kinvey', subject: 'Welcome to Flex Services!'}
const androidPayload = {message: 'You have a new Message', from: 'Kinvey', subject: 'Welcome to BL' }
const push = modules.push;
push.broadcastPayload(iOSAps, iOSExtras, androidPayload, callback);

Push calls are asynchronous in nature. They can be invoked without a callback, but are not guaranteed to complete before continuing to the next statement. However, once invoked they will complete the sending of the push messages, even if the handler function is terminated via a completion handler.

The email module is currently only available for services running on the Kinvey Microservices Runtime and is not available externally or for local testing.

query Module

The Query module allows you to build queries for use in the dataStore module.

const query = new modules.Query();

Operators

All operator methods as exposed by the Query module follow the same pattern: the first argument must be the field under condition, while the other arguments specify the exact condition on that field. All operators return the query itself, so it is easy to concatenate multiple conditions on one line.

For example, to select all models with a rate between 25 and 50:

const query = new modules.Query();
query.greaterThanOrEqualTo('rate', 25).lessThanOrEqualTo('rate', 50);
Comparison Operators
  • equalTo matches if the field is = the supplied value.
  • greaterThan matches if the field is > the supplied value.
  • greaterThanOrEqualTo matches if the field is >= the supplied value.
  • lessThan matches if the field is < the supplied value.
  • lessThanOrEqualTo matches if the field is <= the supplied value.
  • notEqualTo matches if the field is != the supplied value.
  • exists matches if the field exists.
  • mod matches if the field modulo the supplied divisor (second argument) has the supplied remainder (third argument).
  • matches matches if the field matches the supplied regular expression.

Regular expressions need to be anchored (prefixed with ^), and case sensitive. To do case insensitive search, create a normalized (i.e. all lowercase) field in your collection and perform the match on that field.

Array Operators
  • contains matches if any of the supplied values is an element in the field.
  • containsAll matches if the supplied values are all elements in the field.
  • notContainedIn matches if the supplied value is not an element in the field.
  • size matches if the number of elements in the field equals the supplied value.

Modifiers

Query modifiers control how query results are presented. A distinction is made between limit, skip, and sort modifiers.

Limit and Skip

Limit and skip modifiers allow for paging of results. Set the limit to the number of results you want to show per page. The skip modifier indicates how many results are skipped from the beginning.

// Show results 20–40
const query = new modules.Query();
query.limit = 20;
query.skip = 20;
Sort

Query results are sorted either in ascending or descending order. It is possible to add multiple fields to sort on.

// Sort on last name (ascending), then on age (descending).
const query = new modules.Query();
query.ascending('last_name');
query.descending('age');

Data is sorted lexicographically, meaning B comes before a, and 10 before 2.

Field Selection

By default, all fields in an entity will be retrieved. You can, however, specify specific fields to retrieve. This can be useful to save bandwidth.

const query = new modules.Query();
query.fields = [ 'last_name', 'age' ];

Saving entities after retrieving them using Field Selection will result in the loss of all fields not selected. Further, these partial entities will not be available for use with Caching & Offline Saving.

Compound Queries

You can combine filters with modifiers within a single query.

// Returns the first five users with last_name “Doe”, sorted by first_name.
const query = new modules.Query();
query.limit = 5;
query.equalTo('last_name', 'Doe');
query.ascending('first_name');
Joining Operators

It is very easy to join multiple queries into one. In order of precedence, the three joining operators are listed below in order of precendence.

  • and joins two or more queries using a logical AND operation.
  • nor joins two or more queries using a logical NOR operation.
  • or joins two or more queries using a logical OR operation.

The example below demonstrates how to join two separate queries.

const query = new modules.Query();
query.equalTo('last_name', 'Doe');
const secondQuery = new modules.Query();
secondQuery.equalTo('last_name', 'Roe')

// Selects all users with last_name “Doe” or “Roe”.
query.or(secondQuery);

Alternatively, the snippet above can be shortened using the join operator inline.

// Selects all users with last_name “Doe” or “Roe”.
const query = new modules.Query();
query.equalTo('last_name', 'Doe').or().equalTo('last_name', 'Roe');

You can build arbitrary complex queries using any join operators. The rule of thumb is to take the precendence order into account when building queries to make sure the correct results are returned.

Location Queries

Location Queries are only available on fields named _geoloc. The value of this field should be an array holding two elements: longitude and latitude (in that order). Kinvey indexes that attribute for optimal location querying.

The following Location Operators are supported by modules.Query:

  • near matches if the field is within the supplied radius (in miles) of the supplied coordinate.
  • withinBox matches if the field is within the box specified by the two supplied coordinates.
  • withinPolygon matches if the field is within the polygon specified by the supplied list of coordinates.

For example, a query to retrieve all restaurants close (10 mile radius) to a users’ current location could look as follows:

// Get users current position
function handler(context, complete, modules) {
  const coord = [context.body.longitude, context.body.latitude];

  // Query for restaurants close by.
  var query = new modules.Query();
  query.near('_geoloc', coord, 10);

  var dataStore = modules.dataStore().collection('restaurants');
  var stream = dataStore.find(query, (err, result) => {  
    if (err) {
      return complete().setBody(err).runtimeError().done():
    }
    complete().setBody(result).ok().next();
  });
});

requestContext Module

Used to return data and perform functions against the current request context.

MethodBehavior
getAuthenticatedUserId()Returns the ID of the user who submitted the request. When the app or master secret is used to authenticate, the application ID is returned.
getAuthenticatedUsername()Returns the username of the user who submitted the request.
getSecurityContext()Returns the context of the current user. Possible values are user for a user account, app for the app account with app secret, and master for the app account with master secret.

Custom Request Properties

Kinvey Client libraries provide API to pass Custom Request Properties when communicating with Kinvey services. The requestContext module provides methods to read and manipulate those properties.

MethodBehavior
getCustomRequestProperty(propertyName)Takes string value propertyName and returns value of key propertyName from the Custom Request Properties object. Returns undefined if key propertyName does not exist.
setCustomReqeustProperty(propertyName, propertyValue)Creates Custom Request Property propertyName using the provided string propertyName, and assigns propertyValue as its value. If propertyName doesn't exist, it will be created. propertyValue can be any valid JSON value.

The Custom Request Properties object will be available throughout the entire Kinvey Request pipeline. For example, you can add/modify a Custom Request Property in a preFetch hook, and those changes will be available in a postFetch hook on the same transaction.

Example Code
//Assume {officeLocation: 'Paris'} was provided as the Custom Request Properties object

function handlerForPrefetch(context, complete, modules){

  const requestContext = modules.requestContext;
  const officeLocation = requestContext.getCustomRequestProperty('officeLocation');

  if (officeLocation === 'Paris'){
      try {
         //Perform some "preprocessing" logic for requests from Paris
         //...
         //Set didPreprocess to true in the Custom Request Properties
         requestContext.setCustomRequestProperty('didPreprocess', true);
      } catch (error) {
       //If preprocessing fails due to an error, set didPreprocess to false
       requestContext.setCustomRequestProperty('didPreprocess', false);
      }  
  }

  //Continue the execution
  complete().ok().next();

}

The above code checks the Custom Request Properties for a property, officeLocation. If the officeLocation is Paris, then some custom pre-processing is done as part of the pre-fetch hook. If the pre-processing succeeds, we set didPreprocess to true in the Custom Request Properties. If an error is encountered during preprocessing, we set didPreprocess to false.

We could implement a post-fetch hook that looks for the didPreprocess flag on the Custom Request Properties object:

//Assume {officeLocation: 'Paris', didPreprocess: true} was provided as the Custom Request Properties object

function handlerForPostFetch(context, complete, modules){

  const requestContext = modules.requestContext;
  const officeLocation = requestContext.getCustomRequestProperty('officeLocation');
  const didPreprocess = requestContext.getCustomRequestProperty('didPreprocess');

  if (officeLocation === 'Paris'){
      if (didPreprocess === true) {
        //perform some post-processing specific to pre-process success
      } else {
        //optionally perform some other post-processing
      }
  }

  //Finish the execution
  complete().ok().next();

}

For details on passing Custom Request Properties to Kinvey backend services, please see the API reference for the Kinvey Client Library of your choice.

Client App Version

All of the Kinvey client libraries provide API to pass a client-app-specific version string (for implementation details, see the Kinvey Dev Center reference documentation for the library of your choice). The requestContext module provides a clientAppVersion namespace, which includes API to retrieve details about the version of the client app making the request.

Kinvey recommends (but does not require) using version strings that conform to the pattern major.minor.patch, where all values are integers and minor and patch are optional. The majorVersion(), minorVersion() and patchVersion() methods defined below are convenience methods designed to work with version strings conforming to this pattern. If major, minor or patch contain any non-digit characters (i.e. 'v1' contains a non-digit character, 'v'), the corresponding convenience method will return NaN.

The requestContext.clientAppVersion namespace provides the following methods:

MethodBehavior
stringValue()Returns the client app version value as a String. Returns null if no client app version is passed in.
majorVersion()Returns the first dot-separated field of the client app version string (or the entire version string if there are no 'dot' characters) as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
minorVersion()Returns the second dot-separated field of the client-app version string as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
patchVersion()Returns the third dot-separated field of the client-app version string as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
Example Code

Assuming a client app version string of "1.1.5":

function handler(context, complete, modules){
  const requestContext = modules.requestContext;

  const majorVersion = requestContext.clientAppVersion.majorVersion(); //majorVersion will be 1
  const minorVersion = requestContext.clientAppVersion.minorVersion(); //minorVersion will be 1
  const patchVersion = requestContext.clientAppVersion.patchVersion(); //patchVersion will be 5

  if (majorVersion < 2) {               //will be true
    //Perform some version 1 compatible logic
    if ((minorVersion >= 1) && (patchVersion === 5)) {  //Will be true
      //perform some logic for a specific patch release
    }
  } else if (majorVersion >= 2) {           //Will be false
    //Perform some version 2+ compatible logic
  }


  //Finish the execution
  complete().ok().done();

}

Assuming a client app version string of "1.0.1-beta":

function handler(context, response, modules){
  const requestContext = modules.requestContext;

  const versionString = requestContext.clientAppVersion.stringValue(); //versionString will be "1.0.1-beta"
  const majorVersion = requestContext.clientAppVersion.majorVersion();   //majorVersion will be 1
  const patchVersion = requestContext.clientAppVersion.patchVersion();   //patchVersion will be NaN

  if (majorVersion < 2) {             //Will be true
    //Perform some version 1 compatible logic
    if (patchVersion > 1) {           //Will be FALSE as patchVersion is NaN
      //Perform some patch-specific logic
    }
  }

  if (versionString.match('beta') !== null){  //Will be true
    //Perform some beta-specific logic
  }

  //Finish the execution
  complete().ok().done();

}

tempObjectStore Module

A key-value store for persisting temporary data between a pre- and post-hook. The tempObjectStore object is an ephemeral store that only lives for the duration of a single request, but will allow for data that is written in a pre-hook to be read in a FlexData service and/or in the post-hook. The module implements three methods:

MethodBehavior
set(key, value)Writes a value for the given key to the tempObjectStore
get(key)Returns the value of the supplied key
getAll()Returns all key/values in the tempObjectStore as an object

For example, to store an item in a handler:

function handler1(context, complete, modules) {
  var tempObjectStore = modules.tempObjectStore;
  tempObjectStore.set('token', context.body.token);
  complete().ok().next();
}

Then, to retrieve it:

function handler2(context, response, modules) {
  var tempObjectStore = modules.tempObjectStore;
  var token = tempObjectStore.get('token');
  complete().setBody({ message: `Object for token ${token} was saved successfully.` }).ok().done();
}

The Temp Object Store is meant for storing small, temporary amounts of data to be passed between different functions. Large amounts of data or large objects should not be stored in the tempObjectStore for performance reasons.

userStore Module

Use the userStore module to interact with Kinvey Users. The userStore module can be used to create, update, find, remove, suspend, and restore Kinvey users.

To initialize the userStore:

const store = modules.userStore();

The userStore method also takes an optional options argument is an optional object containing store options for the current store. The options are:

OptionDescription
useBlIf set to true, executes BL hooks associated with the userStore request. If false, business logic hooks will not execute. Defaults to false.
useUserContextUses user context credentials to access the userStore if set to true. If false, will execute in the context of mastersecret. Defaults to false.
useMasterSecretDEPRECATED: use useUserContext instead Uses the mastersecret credentials to access the userStore if set to true. If set to false, will use the current user credentials.
skipBlDEPRECATED: use useBl instead If set to true, skips BL processing when accessing the userStore. If false or not included, it will default to executing any BL associated with the store.

Requests that use userContext will automatically execute BL, as requests to the Kinvey platform using a user context cannot skip BL.

For example:

const options = {
  useBl: true,
  useUserContext: true
}

const store = modules.userStore(options);

The userStore object contains methods for accessing Kinvey users. All methods take a callback function.

Most methods take a callback with err and results arguments. The remove, suspend, and restore methods take only an err argument.

MethodDescription
find(query, callback)Finds the users contained in the query. The query is a Query object created with modules.Query. If no Query object is supplied, the find method will return all users in the system, based on the authenticated user (or mastersecret and the _acls of the user collection.
findById(userId, callback)Finds a single user by its _id.
getCurrentUser(callback)Gets the current user's user entity. Note, this method executes in the current user context, regardless of whether the store was set to mastersecret or not.
create(user, callback)Creates the provided user. Note that regardless of whether user or mastersecret credentials are selected for the store, create always uses appsecret credentials.
update(user, callback)Updates the provided user. Note that the user entity supplied must contain an _id.
remove(userId, callback)Removes a single user by its _id. Note this method deletes the user from the userStore and is non-recoverable.
suspend(userId, callback)Suspends a single user by its _id.
restore(userId, callback)Restores a suspended user.
count(query, callback)Gets a count of all users that would be returned by the query. The query is a Query object created with modules.Query. If no Query object is supplied, the count method will return a count of the total number of users.

Circular requests to the userStore (request to the useStore when the originating Flex request was also to the userStore) must be executed under mastersecret credentials and must not use Business Logic. If either useUserContext or useBl is set to true, these types of requests will fail with an error in the callback.

For example:

  const store =  userStore({ useUserContext: true });
  store.findById(1234, (err, result) => {
    if (err) {
      return complete().setBody(err).runtimeError().done();
    }
    result.status = 'Approved';
    store.update(result, (err, savedResult) => {
      if (err) {
        return complete(err).runtimeError().done();
      }
      complete().setBody(savedResult).ok().next();
    });
  });
});

When testing userStore, locally in particular, special headers need to be added to your local tests. These headers will be added automatically by Kinvey in production use. For information on the required headers, see the section on testing locally.

Headers

Kinvey sends various pieces of data to the Flex service for use in certain modules. The data is send as HTTP headers.

HeaderDescriptionClient modules
X-Kinvey-App-MetadataA stringified JSON object containing information about the app context from which this request is being executedbackendContext, dataStore, userStore
X-Kinvey-Original-Request-HeadersA stringified JSON object containing the request headers that belong to the request context of the server-side logic or data requestrequestContext, dataStore, userStore, request argument in all handlers (if you want to access request headers)
X-Kinvey-UsernameThe username of the user making the requestrequestContext, dataStore, userStore
X-Kinvey-User-IdThe user ID of the user making the requestrequestContext, dataStore, userStore
X-Auth-KeyThe shared secret used for accessing this Flex serviceAll modules, if the SDK is initialized with a shared secret

X-Kinvey-App-Metadata Header

The object contained in the X-Kinvey-App-Metadata contains the following properties:

PropertyDescription
_idThe environmentId
appsecretThe app secret
mastersecretThe master secret
baasUrlThe base URL for the Kinvey instance you are using (ex: https://baas.kinvey.com)

X-Kinvey-Original-Request-Headers Header

The X-Kinvey-Original-Request-Headers object can contain any number of custom headers, but typically contains the following:

PropertyDescription
X-Kinvey-Api-VersionThe API version to use (number). This header is important for the dataStore module.
AuthorizationThe authorization string (either Basic or Kinvey auth) for accessing the Kinvey REST service.
X-Kinvey-Client-App-VersionThe Client App Version string
X-Kinvey-Custom-Request-PropertiesThe custom request properties in stringified JSON form
Got a question?