Docs - BACKEND ADMIN - Full Stack Web And Mobile Applications Development, Design, and Consultancy Company in Lebanon

BACKEND ADMIN is a full stack web and mobile applications development, design, and consultancy company. We help big brands build and execute their web and mobile strategy and products.

admin,backend,realtime,database,restful,iot,web,iOS,Android,development,hosting,marketing,security,websites,mobile,applications,platform,engine,analytics,apps,programming,BaaS,SaaS,Gaming,cloud,api,design,branding,sms,emails,e-commerce,payment,gateway,product,research,authentications,cdn,automation,mep,engineering,consultancy

Navigation Menu
REST API Guide

The REST API lets you interact with Backend Admin from anything that can send an HTTP request like web and mobile apps, or any other Internet connected device. There are many things you can do with the REST API. For example:

  • A mobile website can access Backend Admin data from JavaScript.
  • A webserver can show data from Backend Admin on a website.
  • You can upload large amounts of data that will later be consumed in a mobile app.
  • You can download recent data to run your own custom analytics.
  • Applications written in any programming language can interact with data on Backend Admin.
  • You can export all of your data if you no longer want to use Backend Admin.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Quick Reference

All API access is over HTTPS, and accessed via the https://apis.backendadmin.com domain. The relative path prefix /V1/ indicates that we are currently using version 1 of the API.

Objects

URL HTTP Verb Functionality
/1/classes/<className> POST Creating Objects
/1/classes/<className>/<objectId> GET Retrieving Objects
/1/classes/<className>/<objectId> PUT Updating Objects
/1/classes/<className> GET Queries
/1/classes/<className>/<objectId> DELETE Deleting Objects

Users

URL HTTP Verb Functionality
/1/users POST Signing Up
Linking Users
/1/login GET Logging In
/1/logout POST Logging Out
/1/users/<objectId> GET Retrieving Users
/1/users/me GET Validating Session Tokens
Retrieving Current User
/1/users/<objectId> PUT Updating Users
Linking Users
Verifying Emails
/1/users GET Querying Users
/1/users/<objectId> DELETE Deleting Users
/1/requestPasswordReset POST Requesting A Password Reset

Sessions

URL HTTP Verb Functionality
/1/sessions POST Creating Restricted Sessions
/1/sessions/<objectId> GET Retrieving Sessions
/1/sessions/me GET Retrieving Current Session
/1/sessions/<objectId> PUT Updating Sessions
/1/sessions GET Querying Sessions
/1/sessions/<objectId> DELETE Deleting Sessions
/1/sessions/me PUT Pairing with Installation

Roles

URL HTTP Verb Functionality
/1/roles POST Creating Roles
/1/roles/<objectId> GET Retrieving Roles
/1/roles/<objectId> PUT Updating Roles
/1/roles/<objectId> DELETE Deleting Roles

Files

URL HTTP Verb Functionality
/1/files/<fileName> POST Uploading Files

Analytics

URL HTTP Verb Functionality
/1/events/AppOpened POST Analytics
/1/events/<eventName> POST Custom Analytics

Push Notifications

URL HTTP Verb Functionality
/1/push POST Push Notifications

Installations

URL HTTP Verb Functionality
/1/installations POST Uploading Installation Data
/1/installations/<objectId> GET Retrieving Installations
/1/installations/<objectId> PUT Updating Installations
/1/installations GET Querying Installations
/1/installations/<objectId> DELETE Deleting Installations

Cloud Functions

URL HTTP Verb Functionality
/1/functions/<name> POST Calling Cloud Functions
/1/jobs/<name> POST Triggering Background Jobs

Schemas

URL HTTP Verb Functionality
/1/schemas/ GET Fetch All Schemas
/1/schemas/<className> GET Fetch Schema
/1/schemas/<className> POST Create Schema
/1/schemas/<className> PUT Modify Schema
/1/schemas/<className> DELETE Delete Schema

Apps

URL HTTP Verb Functionality
/1/apps/ GET Fetch Apps
/1/apps/<applicationId> GET Fetch App
/1/apps/<applicationId> POST Create App
/1/apps/<applicationId> PUT Modify App

Function Hooks

URL HTTP Verb Functionality
/1/hooks/functions/<functionName> GET Fetch Cloud Functions
/1/hooks/functions/ POST Create Cloud Function
/1/hooks/functions/<functionName> PUT Edit Cloud Function
/1/hooks/functions/<functionName> DELETE Delete Cloud Function

Trigger Hooks

URL HTTP Verb Functionality
/1/hooks/triggers/<className>/<triggerName> GET Fetch Cloud Trigger
/1/hooks/triggers/ POST Create Cloud Trigger
/1/hooks/triggers/<className>/<triggerName> PUT Edit Cloud Trigger
/1/hooks/triggers/<className>/<triggerName> DELETE Delete Cloud Trigger

Request Format

For POST and PUT requests, the request body must be JSON, with the Content-Type header set to application/json .

Authentication is done via HTTP headers. The X-Parse-Application-Id header identifies which application you are accessing, and the X-Parse-REST-API-Key header authenticates the endpoint.

In the examples that follow, the keys for your app are included in the command. You can use the drop-down to construct example code for other apps.

You may also authenticate your REST API requests using basic HTTP authentication. For example, to retrieve an object you could set the URL using your Parse credentials in the following format:

https: //myAppID:javascript-key=myJavaScriptKey@api.parse.com/1/classes/GameScore/Ed1nuqPvcm 

For JavaScript usage, the Parse Cloud supports cross-origin resource sharing , so that you can use these headers in conjunction with XMLHttpRequest.

Response Format

The response format for all requests is a JSON object.

Whether a request succeeded is indicated by the HTTP status code. A 2xx status code indicates success, whereas a 4xx status code indicates failure. When a request fails, the response body is still JSON, but always contains the fields code and error which you can inspect to use for debugging. For example, trying to save an object with invalid keys will return the message:

{ " code ": 105 , " error ": "invalid field name: bl!ng" } 

Calling from Client Apps

You should not use the REST API Key in client apps (i.e. code you distribute to your customers). If the Parse SDK is available for your client platform, we recommend using our SDK instead of the REST API. If you must call the REST API directly from the client, you should use the corresponding client-side Parse key for that plaform (e.g. Client Key for iOS/Android, or .NET Key for Windows/Xamarin/Unity).

If there is no Parse SDK for your client platform, please use your app's Client Key to call the REST API. Requests made with the Client Key, JavaScript Key, or Windows Key are restricted by client-side app settings that you configure in your Parse.com app dashboard. These settings make your app more secure. For example, we recommend that all production apps turn off the "Client Push Enabled" setting to prevent push notifications from being sent from any device using the Client Key, JavaScript Key, or .NET Key, but not the REST API Key. Therefore, if you plan on registering installations to enable Push Notifications for your app, you should not distribute any app code with the REST API key embedded in it.

The JavaScript Key cannot be used to make requests directly against the REST API from JavaScript. The JavaScript Key is meant to be used with the Parse JavaScript SDK, which makes its posts through a Cross Origin-friendly format without HTTP headers.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Objects

Object Format

Storing data through the Parse REST API is built around a JSON encoding of the object's data. This data is schemaless, which means that you don't need to specify ahead of time what keys exist on each object. You simply set whatever key-value pairs you want, and the backend will store it.

For example, let's say you're tracking high scores for a game. A single object could contain:

{ " score ": 1337 , " playerName ": "Sean Plott" , " cheatMode ": false } 

Keys must be alphanumeric strings. Values can be anything that can be JSON-encoded.

Each object has a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore . We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.

When you retrieve objects from Parse, some fields are automatically added: createdAt , updatedAt , and objectId . These field names are reserved, so you cannot set them yourself. The object above could look like this when retrieved:

{ " score ": 1337 , " playerName ": "Sean Plott" , " cheatMode ": false , " createdAt ": "2011-08-20T02:06:57.931Z" , " updatedAt ": "2011-08-20T02:06:57.931Z" , " objectId ": "Ed1nuqPvcm" } 

createdAt and updatedAt are UTC timestamps stored in ISO 8601 format with millisecond precision: YYYY-MM-DDTHH:MM:SS.MMMZ . objectId is a string unique to this class that identifies this object.

In the REST API, the class-level operations operate on a resource based on just the class name. For example, if the class name is GameScore , the class URL is:

https: //api.parse.com/1/classes/GameScore 

Users have a special class-level url:

https: //api.parse.com/1/users 

The operations specific to a single object are available a nested URL. For example, operations specific to the GameScore above with objectId equal to Ed1nuqPvcm would use the object URL:

https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 

Creating Objects

To create a new object on Parse, send a POST request to the class URL containing the contents of the object. For example, to create the object described above:

 curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "score" : 1337 , "playerName" : "Sean Plott" , "cheatMode" : false }' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/classes/GameScore' , json.dumps({ "score" : 1337 , "playerName" : "Sean Plott" , "cheatMode" : False }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) results = json.loads(connection.getresponse().read()) print  results 

When the creation is successful, the HTTP response is a 201 Created and the Location header contains the object URL for the new object:

Status : 201 Created Location : https://api.parse.com/1/classes/GameScore/Ed1nuqPvcm 

The response body is a JSON object containing the objectId and the createdAt timestamp of the newly-created object:

{ " createdAt ": "2011-08-20T02:06:57.931Z" , " objectId ": "Ed1nuqPvcm" } 

Retrieving Objects

Once you've created an object, you can retrieve its contents by sending a GET request to the object URL returned in the location header. For example, to retrieve the object we created above:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/classes/GameScore/Ed1nuqPvcm' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing all the user-provided fields, plus the createdAt , updatedAt , and objectId fields:

{ " score ": 1337 , " playerName ": "Sean Plott" , " cheatMode ": false , " skills ": [ "pwnage" , "flying"  ] , " createdAt ": "2011-08-20T02:06:57.931Z" , " updatedAt ": "2011-08-20T02:06:57.931Z" , " objectId ": "Ed1nuqPvcm" } 

When retrieving objects that have pointers to children, you can fetch child objects by using the include option. For instance, to fetch the object pointed to by the "game" key:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'include=game'  \ https://api.parse.com/ 1 /classes/GameScore/Ed1nuqPvcm 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "include" : "game" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore/Ed1nuqPvcm?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Updating Objects

To change the data on an object that already exists, send a PUT request to the object URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the object's data. For example, if we wanted to change the score field of our object:

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"score":73453}'  \ https://api.parse.com/ 1 /classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "score" : 73453  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing just an updatedAt field with the timestamp of the update.

{ " updatedAt ": "2011-08-21T18:02:52.248Z" } 

Counters

To help with storing counter-type data, Parse provides the ability to atomically increment (or decrement) any number field. So, we can increment the score field like so:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "score" :{ "__op" : "Increment" , "amount" : 1 }}' \ https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "score" : { "__op" : "Increment" , "amount" : 1  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

To decrement the counter, use the Increment operator with a negative number:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "score" :{ "__op" : "Increment" , "amount" :- 1 }}' \ https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "score" : { "__op" : "Increment" , "amount" : - 1  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Arrays

To help with storing array data, there are three operations that can be used to atomically change an array field:

  • Add appends the given array of objects to the end of an array field.
  • AddUnique adds only the given objects which aren't already contained in an array field to that field. The position of the insert is not guaranteed.
  • Remove removes all instances of each given object from an array field.

Each method takes an array of objects to add or remove in the "objects" key. For example, we can add items to the set-like "skills" field like so:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "skills" :{ "__op" : "AddUnique" , "objects" :[ "flying" , "kungfu" ]}}' \ https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "skills" : { "__op" : "AddUnique" , "objects" : [ "flying" , "kungfu"  ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Relations

In order to update Relation types, Parse provides special operators to atomically add and remove objects to a relation. So, we can add an object to a relation like so:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "opponents" :{ "__op" : "AddRelation" , "objects" :[{ "__type" : "Pointer" , "className" : "Player" , "objectId" : "Vx4nudeWn" }]}}' \ https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "opponents" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "Player" , "objectId" : "Vx4nudeWn"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

To remove an object from a relation, you can do:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "opponents" :{ "__op" : "RemoveRelation" , "objects" :[{ "__type" : "Pointer" , "className" : "Player" , "objectId" : "Vx4nudeWn" }]}}' \ https: //api.parse.com/1/classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "opponents" : { "__op" : "RemoveRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "Player" , "objectId" : "Vx4nudeWn"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Deleting Objects

To delete an object from the Parse Cloud, send a DELETE request to its object URL. For example:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/classes/GameScore/Ed1nuqPvcm' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can delete a single field from an object by using the Delete operation:

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"opponents":{"__op":"Delete"}}'  \ https://api.parse.com/ 1 /classes/GameScore/Ed1nuqPvcm 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/classes/GameScore/Ed1nuqPvcm' , json.dumps({ "opponents" : { "__op" : "Delete"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Batch Operations

To reduce the amount of time spent on network round trips, you can create, update, or delete up to 50 objects in one call, using the batch endpoint.

Each command in a batch has method , path , and body parameters that specify the HTTP command that would normally be used for that command. The commands are run in the order they are given. For example, to create a couple of GameScore objects:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "requests" : [ { "method" : "POST" , "path" : "/1/classes/GameScore" , "body" : { "score" : 1337 , "playerName" : "Sean Plott"  } }, { "method" : "POST" , "path" : "/1/classes/GameScore" , "body" : { "score" : 1338 , "playerName" : "ZeroCool"  } } ] }' \ https: //api.parse.com/1/batch 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/batch' , json.dumps({ "requests" : [ { "method" : "POST" , "path" : "/1/classes/GameScore" , "body" : { "score" : 1337 , "playerName" : "Sean Plott"  } }, { "method" : "POST" , "path" : "/1/classes/GameScore" , "body" : { "score" : 1338 , "playerName" : "ZeroCool"  } } ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The response from batch will be a list with the same number of elements as the input list. Each item in the list with be a dictionary with either the success or error field set. The value of success will be the normal response to the equivalent REST command:

{ " success ": { " createdAt ": "2012-06-15T16:59:11.276Z" , " objectId ": "YAfSAWwXbL" } } 

The value of error will be an object with a numeric code and error string:

{ " error ": { " code ": 101 , " error ": "object not found for delete" } } 

Other commands that work in a batch are update and delete .

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "requests" : [ { "method" : "PUT" , "path" : "/1/classes/GameScore/Ed1nuqPvcm" , "body" : { "score" : 999999  } }, { "method" : "DELETE" , "path" : "/1/classes/GameScore/Cpl9lrueY5"  } ] }' \ https: //api.parse.com/1/batch 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/batch' , json.dumps({ "requests" : [ { "method" : "PUT" , "path" : "/1/classes/GameScore/Ed1nuqPvcm" , "body" : { "score" : 999999  } }, { "method" : "DELETE" , "path" : "/1/classes/GameScore/Cpl9lrueY5"  } ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Note that N requests sent in a batch will still count toward your request limit as N requests.

Data Types

So far we have only used values that can be encoded with standard JSON. The Parse mobile client libraries also support dates, geolocations, and relational data. In the REST API, these values are encoded as JSON hashes with the __type field set to indicate their type, so you can read or write these fields if you use the correct encoding. Overall, the following types are allowed for each field in your object:

  • String
  • Number
  • Boolean
  • Arrays
  • JSON Objects
  • DateTime
  • File
  • Pointer to another Parse Object
  • Relation to another Parse Class
  • Null

The Date type contains a field iso which contains a UTC timestamp stored in ISO 8601 format with millisecond precision: YYYY-MM-DDTHH:MM:SS.MMMZ .

{ " __type ": "Date" , " iso ": "2011-08-21T18:02:52.249Z" } 

Dates are useful in combination with the built-in createdAt and updatedAt fields. For example, to retrieve objects created since a particular time, just encode a Date in a comparison query:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "createdAt" :{ "$gte" :{ "__type" : "Date" , "iso" : "2011-08-21T18:02:52.249Z" }}}' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "createdAt" : { " $gte " : { "__type" : "Date" , "iso" : "2011-08-21T18:02:52.249Z"  } } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The Pointer type is used when mobile code sets a ParseObject as the value of another object. It contains the className and objectId of the referred-to value.

{ " __type ": "Pointer" , " className ": "GameScore" , " objectId ": "Ed1nuqPvc" } 

Note that the bult-in User, Role, and Installation classes are prefixed by an underscore. For example, pointers to user objects have a className of _User . Prefixing with an underscore is forbidden for developer-defined classes and signifies the class is a special built-in.

The Relation type is used for many-to-many relations when the mobile uses PFRelation or ParseRelation as a value. It has a className that is the class name of the target objects.

{ " __type ": "Relation" , " className ": "GameScore" } 

When querying, Relation objects behave like arrays of Pointers. Any operation that is valid for arrays of pointers (other than include ) works for Relation objects.

We do not recommend storing large pieces of binary data like images or documents on a Parse object. Parse objects should not exceed 128 kilobytes in size. To store more, we recommend you use File . You may associate a previously uploaded file using the File type.

{ " __type ": "File" , " name ": "...profile.png" } 

When more data types are added, they will also be represented as hashes with a __type field set, so you may not use this field yourself on JSON objects. For more information about how Parse handles data, check out our documentation on Data .

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Queries

Basic Queries

You can retrieve multiple objects at once by sending a GET request to the class URL. Without any URL parameters, this simply lists objects in the class:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/classes/GameScore' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The return value is a JSON object that contains a results field with a JSON array that lists the objects.

{ " results ": [ { " playerName ": "Jang Min Chul" , " updatedAt ": "2011-08-19T02:24:17.787Z" , " cheatMode ": false , " createdAt ": "2011-08-19T02:24:17.787Z" , " objectId ": "A22v5zRAgd" , " score ": 80075 }, { " playerName ": "Sean Plott" , " updatedAt ": "2011-08-21T18:02:52.248Z" , " cheatMode ": false , " createdAt ": "2011-08-20T02:06:57.931Z" , " objectId ": "Ed1nuqPvcm" , " score ": 73453 } ] } 

Query Constraints

There are several ways to put constraints on the objects found, using the where URL parameter. The value of the where parameter should be encoded JSON. Thus, if you look at the actual URL requested, it would be JSON-encoded, then URL-encoded. The simplest use of the where parameter is constraining the value for keys. For example, if we wanted to retrieve Sean Plott's scores that were not in cheat mode, we could do:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "playerName" : "Sean Plott" , "cheatMode" : false }' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "playerName" : "Sean Plott" , "cheatMode" : False })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The values of the where parameter also support comparisons besides exact matching. Instead of an exact value, provide a hash with keys corresponding to the comparisons to do. The where parameter supports these options:

Key Operation
$lt Less Than
$lte Less Than Or Equal To
$gt Greater Than
$gte Greater Than Or Equal To
$ne Not Equal To
$in Contained In
$nin Not Contained in
$exists A value is set for the key
$select This matches a value for a key in the result of a different query
$dontSelect Requires that a key's value not match a value for a key in the result of a different query
$all Contains all of the given values
$regex Requires that a key's value match a regular expression

For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "score" :{ "$gte" : 1000 , "$lte" : 3000 }}' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "score" : { " $gte " : 1000 , " $lte " : 3000  } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

To retrieve scores equal to an odd number below 10, we could issue:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "score" :{ "$in" :[ 1 , 3 , 5 , 7 , 9 ]}}' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "score" : { " $in " : [ 1 , 3 , 5 , 7 , 9  ] } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

To retrieve scores not by a given list of players we could issue:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "playerName" : { "$nin" : [ "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon"  ] } }' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "playerName" : { " $nin " : [ "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon"  ] } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

To retrieve documents with the score set, we could issue:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "score" :{ "$exists" : true }}' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "score" : { " $exists " : True } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

To retrieve documents without the score set, we could issue:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "score" :{ "$exists" : false }}' \ https: //api.parse.com/1/classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "score" : { " $exists " : False } })}) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

If you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "hometown" :{ "$select" :{ "query" :{ "className" : "Team" , "where" :{ "winPct" :{ "$gt" : 0.5 }}}, "key" : "city" }}}' \ https: //api.parse.com/1/classes/_User 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "hometown" : { " $select " : { "query" : { "className" : "Team" , "where" : { "winPct" : { " $gt " : 0.5  } } }, "key" : "city"  } } })}) connection.connect() connection.request( 'GET' , '/1/classes/_User?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

In addition to where , there are several parameters you can use to configure what types of results are returned by the query.

Parameter Use
order Specify a field to sort by
limit Limit the number of objects returned by the query
skip Use with limit to paginate through results
keys Restrict the fields returned by the query
include Use on Pointer columns to return the full object

You can use the order parameter to specify a field to sort by. Prefixing with a negative sign reverses the order. Thus, to retrieve scores in ascending order:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'order=score'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "order" : "score" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

And to retrieve scores in descending order:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'order=-score'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "order" : "-score" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can sort by multiple fields by passing order a comma-separated list. To retrieve documents that are ordered by scores in ascending order and the names in descending order:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'order=score,-name'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "order" : "score,-name" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can use the limit and skip parameters for pagination. limit defaults to 100, but anything from 1 to 1000 is a valid limit. Thus, to retrieve 200 objects after skipping the first 400:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'limit=200'  \ --data-urlencode 'skip=400'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "limit" : 200 , "skip" : 400 }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can restrict the fields returned by passing keys a comma-separated list. To retrieve documents that contain only the score and playerName fields (and also special built-in fields such as objectId , createdAt , and updatedAt ):

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'keys=score,playerName'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "keys" : "score,playerName" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

All of these parameters can be used in combination with each other. For example:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'where={ "playerName": { "$nin": [ "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" ] } }'  \ --data-urlencode 'order=score,-name'  \ --data-urlencode 'limit=200'  \ --data-urlencode 'skip=400'  \ --data-urlencode 'keys=score,playerName'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "playerName" : { " $nin " : [ "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon"  ] } }), "order" : "score,-name" , "limit" : 200 , "skip" : 400 , "keys" : "score,playerName" }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Queries on Array Values

For keys with an array type, you can find objects where the key's array value contains 2 by:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'where={"arrayKey":2}'  \ https://api.parse.com/ 1 /classes/RandomObject 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "arrayKey" : 2  })}) connection.connect() connection.request( 'GET' , '/1/classes/RandomObject?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can also use the $all operator to find objects with an array field which contains each of the values 2, 3, and 4 by:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "arrayKey" :{ "$all" :[ 2 , 3 , 4 ]}}' \ https: //api.parse.com/1/classes/RandomObject 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "arrayKey" : { " $all " : [ 2 , 3 , 4  ] } })}) connection.connect() connection.request( 'GET' , '/1/classes/RandomObject?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Queries on String Values

If you're trying to implement a generic search feature, we recommend taking a look at this blog post: Implementing Scalable Search on a NoSQL Backend .

Use the $regex operator to restrict to string values that match a regular expression. Most regular expression queries in Parse are heavily throttled due to performance considerations. Use case sensitive, anchored queries where possible. Similar to a MySQL LIKE operator, anchored queries are indexed so they are efficient for large datasets. For example:

# Finds  barbecue sauces that start with "Big Daddy"  curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "name" :{ "$regex" : "^Big Daddy" }}' \ https: //api.parse.com/1/classes/BarbecueSauce 
# Finds barbecue sauces that start with "Big Daddy"  import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "name" : { " $regex " : "^Big Daddy"  } })}) connection.connect() connection.request( 'GET' , '/1/classes/BarbecueSauce?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The above example will match any BarbecueSauce objects where the value in the "name" String key starts with "Big Daddy". For example, both "Big Daddy" and "Big Daddy's" will match, but "big daddy" or "BBQ Sauce: Big Daddy's" will not.

Queries that have regular expression constraints are very expensive, especially for classes with over 100,000 records. Parse restricts how many such operations can be run on a particular app at any given time.

Relational Queries

There are several ways to issue queries for relational data. If you want to retrieve objects where a field matches a particular object, you can use a where clause with a Pointer encoded with __type just like you would use other data types. For example, if each Comment has a Post object in its post field, you can fetch comments for a particular Post :

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "post" :{ "__type" : "Pointer" , "className" : "Post" , "objectId" : "8TOXdXf3tz" }}' \ https: //api.parse.com/1/classes/Comment 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "post" : { "__type" : "Pointer" , "className" : "Post" , "objectId" : "8TOXdXf3tz"  } })}) connection.connect() connection.request( 'GET' , '/1/classes/Comment?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

If you want to retrieve objects where a field contains an object that matches another query, you can use the $inQuery operator. Note that the default limit of 100 and maximum limit of 1000 apply to the inner query as well, so with large data sets you may need to construct queries carefully to get the desired behavior. For example, imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post. You can find comments on posts with images by doing:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "post" :{ "$inQuery" :{ "where" :{ "image" :{ "$exists" : true }}, "className" : "Post" }}}' \ https: //api.parse.com/1/classes/Comment 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "post" : { " $inQuery " : { "where" : { "image" : { " $exists " : True } }, "className" : "Post"  } } })}) connection.connect() connection.request( 'GET' , '/1/classes/Comment?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

If you want to retrieve objects where a field contains an object that does not match another query, you can use the $notInQuery operator. Imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post. You can find comments on posts without images by doing:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "post" :{ "$notInQuery" :{ "where" :{ "image" :{ "$exists" : true }}, "className" : "Post" }}}' \ https: //api.parse.com/1/classes/Comment 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "post" : { " $notInQuery " : { "where" : { "image" : { " $exists " : True } }, "className" : "Post"  } } })}) connection.connect() connection.request( 'GET' , '/1/classes/Comment?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

If you want to retrieve objects that are members of Relation field of a parent object, you can use the $relatedTo operator. Imagine you have a Post class and User class, where each Post can be liked by many users. If the Users that liked a Post were stored in a Relation on the post under the key "likes", you can find the users that liked a particular post by:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "$relatedTo" :{ "object" :{ "__type" : "Pointer" , "className" : "Post" , "objectId" : "8TOXdXf3tz" }, "key" : "likes" }}' \ https: //api.parse.com/1/users 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ " $relatedTo " : { "object" : { "__type" : "Pointer" , "className" : "Post" , "objectId" : "8TOXdXf3tz"  }, "key" : "likes"  } })}) connection.connect() connection.request( 'GET' , '/1/users?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

In some situations, you want to return multiple types of related objects in one query. You can do this by passing the field to include in the include parameter. For example, let's say you are retrieving the last ten comments, and you want to retrieve their related posts at the same time:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'order=-createdAt'  \ --data-urlencode 'limit=10'  \ --data-urlencode 'include=post'  \ https://api.parse.com/ 1 /classes/Comment 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "order" : "-createdAt" , "limit" : 10 , "include" : "post" }) connection.connect() connection.request( 'GET' , '/1/classes/Comment?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Instead of being represented as a Pointer , the post field is now expanded into the whole object. __type is set to Object and className is provided as well. For example, a Pointer to a Post could be represented as:

{ " __type ": "Pointer" , " className ": "Post" , " objectId ": "8TOXdXf3tz" } 

When the query is issued with an include parameter for the key holding this pointer, the pointer will be expanded to:

{ " __type ": "Object" , " className ": "Post" , " objectId ": "8TOXdXf3tz" , " createdAt ": "2011-12-06T20:59:34.428Z" , " updatedAt ": "2011-12-06T20:59:34.428Z" , " otherFields ": "willAlsoBeIncluded" } 

You can also do multi level includes using dot notation. If you wanted to include the post for a comment and the post's author as well you can do:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'order=-createdAt'  \ --data-urlencode 'limit=10'  \ --data-urlencode 'include=post.author'  \ https://api.parse.com/ 1 /classes/Comment 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "order" : "-createdAt" , "limit" : 10 , "include" : "post.author" }) connection.connect() connection.request( 'GET' , '/1/classes/Comment?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

You can issue a query with multiple fields included by passing a comma-separated list of keys as the include parameter.

Counting Objects

Caveat: Count queries are rate limited to a maximum of 160 requests per minute. They can also return inaccurate results for classes with more than 1,000 objects. Thus, it is preferable to architect your application to avoid this sort of count operation (by using counters, for example.)

If you are limiting your query, or if there are a very large number of results, and you want to know how many total results there are without returning them all, you can use the count parameter. For example, if you only care about the number of games played by a particular player:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -G \ --data-urlencode 'where={"playerName":"Jonathan Walsh"}'  \ --data-urlencode 'count=1'  \ --data-urlencode 'limit=0'  \ https://api.parse.com/ 1 /classes/GameScore 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "playerName" : "Jonathan Walsh"  }), "count" : 1 , "limit" : 0 }) connection.connect() connection.request( 'GET' , '/1/classes/GameScore?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Since this requests a count as well as limiting to zero results, there will be a count but no results in the response.

{ " results ": [] , " count ": 1337 } 

With a nonzero limit, that request would return results as well as the count.

Compound Queries

If you want to find objects that match one of several queries, you can use $or operator, with a JSONArray as its value. For instance, if you want to find players with either have a lot of wins or a few wins, you can do:

curl -X GET \ -H "X-Parse-Application-Id : ${APPLICATION_ID} " \ -H " X-Parse-REST-API-Key: ${REST_API_KEY} " \ -G \ --data-urlencode 'where={" $or ":[{" wins ":{" $gt ":150}},{" wins ":{" $lt ":5}}]}' \ https://api.parse.com/1/classes/Player 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ " $or " : [ { "wins" : { " $gt " : 150  } }, { "wins" : { " $lt " : 5  } } ] })}) connection.connect() connection.request( 'GET' , '/1/classes/Player?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Any other constraints on the query are also applied to the object returned, so you can add other constraints to queries with $or .

Note that we do not, however, support GeoPoint or non-filtering constraints (e.g. nearSphere, within, limit, skip, sort, include) in the subqueries of the compound query.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Users

Some ParseUser feature like ParseUser.CurrentUser and ParseUser.LogIn are not working properly in ASP.NET due to its server-side nature.

Many apps have a unified login that works across the mobile app and other systems. Accessing user accounts through the REST API lets you build this functionality on top of Parse.

In general, users have the same features as other objects, such as the flexible schema. The differences are that user objects must have a username and password, the password is automatically encrypted and stored securely, and Parse enforces the uniqueness of the username and email fields.

Signing Up

Signing up a new user differs from creating a generic object in that the username and password fields are required. The password field is handled differently than the others; it is encrypted with bcrypt when stored in the Parse Cloud and never returned to any client request.

You can ask Parse to verify user email addresses in your application settings page. With this setting enabled, all new user registrations with an email field will generate an email confirmation at that address. You can check whether the user has verified their email with the emailVerified field.

To sign up a new user, send a POST request to the users root. You may add any additional fields. For example, to create a user with a specific phone number:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "X-Parse-Revocable-Session: 1"  \ - H "Content-Type: application/json"  \ -d '{ "username" : "cooldude6" , "password" : "p_n7!-e8" , "phone" : "415-392-0202" }' \ https: //api.parse.com/1/users 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/users' , json.dumps({ "username" : "cooldude6" , "password" : "p_n7!-e8" , "phone" : "415-392-0202"  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Revocable-Session" : "1" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The X-Parse-Revocable-Session header tells Parse to return a revocable session even if your app has "Require Revocable Sessions" turned off (at Parse.com app settings page). This is useful for transitioning from legacy session tokens to revocable sessions when your existing mobile app also accesses the same Parse data. If "Require Revocable Sessions" is turned on (default for new apps), the X-Parse-Revocable-Session header is unnecessary. When you ask for a revocable session during signup, the Parse Cloud will automatically create a ParseSession object. On this request, you can also tell Parse to automatically attach an installation to that session by specifying the optional X-Parse-Installation-Id header with the installationId of that installation.

When the creation is successful, the HTTP response is a 201 Created and the Location header contains the URL for the new user:

Status : 201 Created Location : https://api.parse.com/1/users/g7y9tkhB7O 

The response body is a JSON object containing the objectId , the createdAt timestamp of the newly-created object, and the sessionToken which can be used to authenticate subsequent requests as this user:

{ " createdAt ": "2011-11-07T20:58:34.448Z" , " objectId ": "g7y9tkhB7O" , " sessionToken ": "r:pnktnjyb996sj4p156gjtp4im" } 

Logging In

After you allow users to sign up, you need to let them log in to their account with a username and password in the future. To do this, send a GET request to the /1/login endpoint with username and password as URL-encoded parameters:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Revocable-Session: 1"  \ -G \ --data-urlencode 'username=cooldude6'  \ --data-urlencode 'password=p_n7!-e8'  \ https://api.parse.com/ 1 /login 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "username" : "cooldude6" , "password" : "p_n7!-e8" }) connection.connect() connection.request( 'GET' , '/1/login?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Revocable-Session" : "1"  }) result = json.loads(connection.getresponse().read()) print  result 

The X-Parse-Revocable-Session header tells Parse to return a revocable session even if your app has "Require Revocable Sessions" turned off (at Parse.com app settings page). This is useful for transitioning from legacy session tokens to revocable sessions when your existing mobile app also accesses the same Parse data. If "Require Revocable Sessions" is turned on (default for new apps), the X-Parse-Revocable-Session header is unnecessary. When you ask for a revocable session during login, the Parse Cloud will automatically create a ParseSession object. On this request, you can also tell Parse to automatically attach an installation to that session by specifying the optional X-Parse-Installation-Id header with the installationId of that installation.

The response body is a JSON object containing all the user-provided fields except password . It also contains the createdAt , updatedAt , objectId , and sessionToken fields:

{ " username ": "cooldude6" , " phone ": "415-392-0202" , " createdAt ": "2011-11-07T20:58:34.448Z" , " updatedAt ": "2011-11-07T20:58:34.448Z" , " objectId ": "g7y9tkhB7O" , " sessionToken ": "r:pnktnjyb996sj4p156gjtp4im" } 

Verifying Emails

Enabling email verification in an application's settings allows the application to reserve part of its experience for users with confirmed email addresses. Email verification adds the emailVerified field to the User object. When a User 's email is set or modified, emailVerified is set to false . Parse then emails the user a link which will set emailVerified to true .

There are three emailVerified states to consider:

  1. true - the user confirmed his or her email address by clicking on the link Parse emailed them. Users can never have a true value when the user account is first created.
  2. false - at the time the User object was last refreshed, the user had not confirmed his or her email address. If emailVerified is false , consider refreshing the User object.
  3. missing - the User was created when email verification was off or the User does not have an email .

Requesting A Password Reset

You can initiate password resets for users who have emails associated with their account. To do this, send a POST request to /1/requestPasswordReset endpoint with email in the body of the request:

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"email":"coolguy@iloveapps.com"}'  \ https://api.parse.com/ 1 /requestPasswordReset 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/requestPasswordReset' , json.dumps({ "email" : "coolguy@iloveapps.com"  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

If successful, the response body is an empty JSON object.

Retrieving Users

You can also retrieve the contents of a user object by sending a GET request to the URL returned in the location header when it was created. For example, to retrieve the user created above:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /users/g7y9tkhB7O 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/users/g7y9tkhB7O' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing all the user-provided fields except password . It also contains the createdAt , updatedAt , and objectId fields:

{ " username ": "cooldude6" , " phone ": "415-392-0202" , " createdAt ": "2011-11-07T20:58:34.448Z" , " updatedAt ": "2011-11-07T20:58:34.448Z" , " objectId ": "g7y9tkhB7O" } 

Validating Session Tokens / Retrieving Current User

With a valid session token, you can send a GET request to the /1/users/me endpoint to retrieve the user associated with that session token:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /users/me 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/users/me' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

The response matches the JSON object above for retrieving users. If the session token is not valid, an error object is returned:

{ " code ": 209 , " error ": "invalid session token" } 

Updating Users

In normal usage, nobody except the user is allowed to modify their own data. To authenticate themselves, the user must add a X-Parse-Session-Token header to the request with the session token provided by the signup or login method.

To change the data on a user that already exists, send a PUT request to the user URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the user's data. username and password may be changed, but the new username must not already be in use.

For example, if we wanted to change the phone number for cooldude6 :

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ -H "Content-Type: application/json"  \ -d '{"phone":"415-369-6201"}'  \ https://api.parse.com/ 1 /users/g7y9tkhB7O 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/users/g7y9tkhB7O' , json.dumps({ "phone" : "415-369-6201"  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing just an updatedAt field with the timestamp of the update.

{ " updatedAt ": "2011-11-07T21:25:10.623Z" } 

Querying

You can retrieve multiple users at once by sending a GET request to the root users URL. Without any URL parameters, this simply lists users:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /users 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/users' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The return value is a JSON object that contains a results field with a JSON array that lists the objects.

{ " results ": [ { " username ": "bigglesworth" , " phone ": "650-253-0000" , " createdAt ": "2011-11-07T20:58:06.445Z" , " updatedAt ": "2011-11-07T20:58:06.445Z" , " objectId ": "3KmCvT7Zsb" }, { " username ": "cooldude6" , " phone ": "415-369-6201" , " createdAt ": "2011-11-07T20:58:34.448Z" , " updatedAt ": "2011-11-07T21:25:10.623Z" , " objectId ": "g7y9tkhB7O" } ] } 

All of the options for queries that work for regular objects also work for user objects, so check the section on Querying Objects for more details.

Deleting Users

To delete a user from the Parse Cloud, send a DELETE request to its URL. You must provide the X-Parse-Session-Token header to authenticate. For example:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /users/g7y9tkhB7O 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/users/g7y9tkhB7O' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

Linking Users

Parse allows you to link your users with services like Twitter and Facebook, enabling your users to sign up or log into your application using their existing identities. This is accomplished through the sign-up and update REST endpoints by providing authentication data for the service you wish to link to a user in the authData field. Once your user is associated with a service, the authData for the service will be stored with the user and is retrievable by logging in.

authData is a JSON object with keys for each linked service containing the data below. In each case, you are responsible for completing the authentication flow (e.g. OAuth 1.0a) to obtain the information the the service requires for linking.

Facebook authData

{ " facebook ": { " id ": "user's Facebook id number as a string" , " access_token ": "an authorized Facebook access token for the user" , " expiration_date ": "token expiration date of the format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" } } 

Learn more about Facebook login .

Twitter authData

{ " twitter ": { " id ": "user's Twitter id number as a string" , " screen_name ": "user's Twitter screen name" , " consumer_key ": "your application's consumer key" , " consumer_secret ": "your application's consumer secret" , " auth_token ": "an authorized Twitter token for the user with your application" , " auth_token_secret ": "the secret associated with the auth_token" } } 

Learn more about Twitter login .

Anonymous user authData

{ " anonymous ": { " id ": "random UUID with lowercase hexadecimal digits" } } 

Signing Up and Logging In

Signing a user up with a linked service and logging them in with that service uses the same POST request, in which the authData for the user is specified. For example, to sign up or log in with a user's Twitter account:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "X-Parse-Revocable-Session: 1"  \ - H "Content-Type: application/json"  \ -d '{ "authData" : { "twitter" : { "id" : "12345678" , "screen_name" : "ParseIt" , "consumer_key" : "SaMpLeId3X7eLjjLgWEw" , "consumer_secret" : "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c" , "auth_token" : "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU" , "auth_token_secret" : "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"  } } }' \ https: //api.parse.com/1/users 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/users' , json.dumps({ "authData" : { "twitter" : { "id" : "12345678" , "screen_name" : "ParseIt" , "consumer_key" : "SaMpLeId3X7eLjjLgWEw" , "consumer_secret" : "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c" , "auth_token" : "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU" , "auth_token_secret" : "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU"  } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Revocable-Session" : "1" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The X-Parse-Revocable-Session header tells Parse to return a revocable session even if your app has "Require Revocable Sessions" turned off (at Parse.com app settings page). This is useful for transitioning from legacy session tokens to revocable sessions when your existing mobile app also accesses the same Parse data. If "Require Revocable Sessions" is turned on (default for new apps), the X-Parse-Revocable-Session header is unnecessary. When you ask for a revocable session during login or signup, the Parse Cloud will automatically create a ParseSession object. On this request, you can also tell Parse to automatically attach an installation to that session by specifying the optional X-Parse-Installation-Id header with the installationId of that installation.

Parse then verifies that the provided authData is valid and checks to see if a user is already associated with this data. If so, it returns a status code of 200 OK and the details (including a sessionToken for the user):

Status : 200 OK Location : https://api.parse.com/1/users/uMz0YZeAqc 

With a response body like:

{ " username ": "Parse" , " createdAt ": "2012-02-28T23:49:36.353Z" , " updatedAt ": "2012-02-28T23:49:36.353Z" , " objectId ": "uMz0YZeAqc" , " sessionToken ": "r:samplei3l83eerhnln0ecxgy5" , " authData ": { " twitter ": { " id ": "12345678" , " screen_name ": "ParseIt" , " consumer_key ": "SaMpLeId3X7eLjjLgWEw" , " consumer_secret ": "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c" , " auth_token ": "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU" , " auth_token_secret ": "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU" } } } 

If the user has never been linked with this account, you will instead receive a status code of 201 Created , indicating that a new user was created:

Status : 201 Created Location : https://api.parse.com/1/users/uMz0YZeAqc 

The body of the response will contain the objectId , createdAt , sessionToken , and an automatically-generated unique username . For example:

{ " username ": "iwz8sna7sug28v4eyu7t89fij" , " createdAt ": "2012-02-28T23:49:36.353Z" , " objectId ": "uMz0YZeAqc" , " sessionToken ": "r:samplei3l83eerhnln0ecxgy5" } 

Linking

Linking an existing user with a service like Facebook or Twitter uses a PUT request to associate authData with the user. For example, linking a user with a Facebook account would use a request like this:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "X-Parse-Session-Token: r:samplei3l83eerhnln0ecxgy5"  \ - H "Content-Type: application/json"  \ -d '{ "authData" : { "facebook" : { "id" : "123456789" , "access_token" : "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL" , "expiration_date" : "2012-02-28T23:49:36.353Z"  } } }' \ https: //api.parse.com/1/users/uMz0YZeAqc 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/users/uMz0YZeAqc' , json.dumps({ "authData" : { "facebook" : { "id" : "123456789" , "access_token" : "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL" , "expiration_date" : "2012-02-28T23:49:36.353Z"  } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:samplei3l83eerhnln0ecxgy5" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

After linking your user to a service, you can authenticate them using matching authData .

Unlinking

Unlinking an existing user with a service also uses a PUT request to clear authData from the user by setting the authData for the service to null . For example, unlinking a user with a Facebook account would use a request like this:

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:samplei3l83eerhnln0ecxgy5"  \ -H "Content-Type: application/json"  \ -d '{ "authData": { "facebook": null } }'  \ https://api.parse.com/ 1 /users/uMz0YZeAqc 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/users/uMz0YZeAqc' , json.dumps({ "authData" : { "facebook" : null } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:samplei3l83eerhnln0ecxgy5" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Security

When you access Parse via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify acls via the REST API, just by accessing the "ACL" key of an object.

The ACL is formatted as a JSON object where the keys are either object ids or the special key "*" to indicate public access permissions. The values of the ACL are "permission objects", JSON objects whose keys are the permission names and whose values are always true .

For example, if you want the user with id "3KmCvT7Zsb" to have read and write access to an object, plus the object should be publicly readable, that corresponds to an ACL of:

{ " 3KmCvT7Zsb ": { " read ": true , " write ": true } , " * ": { " read ": true } } 

If you want to access your data ignoring all ACLs, you can use the master key provided on the Dashboard. Instead of the X-Parse-REST-API-Key header, set the X-Parse-Master-Key header. For backward compatibility, you can also do master-level authentication using HTTP Basic Auth, passing the application id as the username and the master key as the password. For security, the master key should not be distributed to end users, but if you are running code in a trusted environment, feel free to use the master key for authentication.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Sessions

Session APIs are only available in apps with revocable sessions enabled. Parse apps created after March 25, 2015 have this enabled by default ("Require Revocable Sessions" toggle in your Parse.com app settings page). If you have an existing app, you can upgrade to revocable sessions by following the Session Migration Tutorial .

Sessions represent an instance of a user logged into a device. Sessions are automatically created when users log in or sign up. They are automatically deleted when users log out. There is one distinct ParseSession object for each user-installation pair; if a user issues a login request from a device they're already logged into, that user's previous ParseSession object for that Installation is automatically deleted. ParseSession objects are stored on Parse in the Session class, and you can view them on the Parse.com Data Browser. We provide a set of APIs to manage ParseSession objects in your app.

ParseSession is a subclass of ParseObject , so you can query, update, and delete sessions in the same way that you manipulate normal objects on Parse. Because the Parse Cloud automatically creates sessions when you log in or sign up users, you should not manually create ParseSession objects unless you are building a "Parse for IoT" app (e.g. Arduino or Embedded C). Deleting a ParseSession will log the user out of the device that is currently using this session's token.

Unlike other Parse objects, the ParseSession class does not have Cloud Code triggers. So you cannot register a beforeSave or afterSave handler for the Session class.

Properties

The ParseSession object has these special fields:

  • sessionToken (readonly): String token for authentication on Parse API requests. In the response of ParseSession queries, only your current ParseSession object will contain a session token.
  • user : (readonly) Pointer to the ParseUser object that this session is for.
  • createdWith (readonly): Information about how this session was created (e.g. { "action": "login", "authProvider": "password"} ).
    • action could have values: login , signup , create , or upgrade . The create action is when the developer manually creates the session by saving a ParseSession object. The upgrade action is when the user is upgraded to revocable session from a legacy session token.
    • authProvider could have values: password , anonymous , facebook , or twitter .
  • restricted (readonly): Boolean for whether this session is restricted.
    • Restricted sessions do not have write permissions on ParseUser , ParseSession , and ParseRole classes on Parse. Restricted sessions also cannot read unrestricted sessions.
    • All sessions that the Parse Cloud automatically creates during user login/signup will be unrestricted. All sessions that the developer manually creates by saving a new ParseSession object from the client (only needed for "Parse for IoT" apps) will be restricted.
  • expiresAt (readonly): Approximate UTC date when this ParseSession object will be automatically deleted. You can configure session expiration settings (either 1-year inactivity expiration or no expiration) in your app's Parse.com dashboard settings page.
  • installationId (can be set only once): String referring to the ParseInstallation where the session is logged in from. For the REST API, you can set this by passing the X-Parse-Installation-Id header on login and signup requests. All special fields except installationId can only be set automatically by the Parse Cloud. You can add custom fields onto ParseSession objects, but please keep in mind that any logged-in device (with session token) can read other sessions that belong to the same user (unless you disable Class-Level Permissions, see below).

Handling Invalid Session Token Error

Apps created before March 25, 2015 use legacy session tokens until you migrate them to use the new revocable sessions. On API requests with legacy tokens, if the token is invalid (e.g. User object was deleted), then the request is executed as a non-logged in user and no error was returned. On API requests with revocable session tokens, an invalid session token will always fail with the "invalid session token" error. This new behavior lets you know when you need to ask the user to log in again.

With revocable sessions, your current session token could become invalid if its corresponding ParseSession object is deleted from the Parse Cloud. This could happen if you implement a Session Manager UI that lets users log out of other devices, or if you manually delete the session via Cloud Code, REST API, or Data Browser. Sessions could also be deleted due to automatic expiration (if configured in app settings). When a device's session token no longer corresponds to a ParseSession object on the Parse Cloud, all API requests from that device will fail with “Error 209: invalid session token”.

Creating Sessions

For mobile apps and websites, you should not create ParseSession objects manually. Instead, you should call GET /1/login and POST /1/users (signup), which will automatically generate a ParseSession object in the Parse Cloud. The session token for this automatically-created session will be sent back on the login and signup response. Same for Facebook/Twitter login and signup requests.

In "Parse for IoT" apps (e.g. Arduino or Embedded C), you may want to programmatically create a restricted session that can be transferred to an IoT device. In order to do this, you must first log in normally to obtain an unrestricted session token. Then, you can create a restricted session by providing this unrestricted session token:

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ -H "Content-Type: application/json"  \ -d '{"customField":"value"}'  \ https://api.parse.com/ 1 /sessions 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/sessions' , json.dumps({ "customField" : "value"  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

In the above code, r:pnktnjyb996sj4p156gjtp4im is the unrestricted session token from the original user login.

The response looks like:

{ " createdAt ": "2015-03-25T18:21:52.883Z" , " createdWith ": { " action ": "create" } , " objectId ": "pla1TY9co3" , " restricted ": true , " sessionToken ": "r:aVrtljyb7E8xKo9256gfvp4n2" } 

At this point, you can pass the session token r:aVrtljyb7E8xKo9256gfvp4n2 to an IoT device so that it can access the current user's data.

Retrieving Sessions

If you have the session's objectId, you fetch the ParseSession object as long as it belongs to the same user as your current session:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /sessions/Axy98kq1B09 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/sessions/Axy98kq1B09' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

If you only have the session's token (from previous login or session create), you can validate and fetch the corresponding session by:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /sessions/me 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/sessions/me' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

Updating Sessions

Updating a session is analogous to updating a Parse object.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ -H "Content-Type: application/json"  \ -d '{"customField":"value"}'  \ https://api.parse.com/ 1 /sessions/Axy98kq1B09 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/logout' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

Querying Sessions

Querying for ParseSession objects will only return objects belonging to the same user as your current session (due to the Session ACL). You can also add a where clause to your query, just like normal Parse objects.

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /sessions 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/sessions' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

Deleting Sessions

Deleting the Session object will revoke its session token and cause the user to be logged out on the device that's currently using this session token. When you have the session token, then you can delete its ParseSession object by calling the logout endpoint:

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 / logout 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/logout' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

If you want to delete another ParseSession object for your user, and you have its objectId , you can delete it (but not log yourself out) by:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /sessions/Axy98kq1B09 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/sessions/Axy98kq1B09' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

X-Parse-Session-Token authenticates the request as the user that also owns session Axy98kq1B09 , which may have a different session token. You can only delete other sessions that belong to the same user.

Pairing Session with Installation

For normal user login with the /1/login endpoint, the Parse Cloud will set the automatically-created ParseSession object's installationId to the X-Parse-Installation-Id header passed on the login or signup request. Therefore, for these scenarios, you don't need to manually associate the ParseSession object with an installation.

The following API is most useful for "Parse for IoT" apps (e.g. Arduino or Embedded C). During IoT device provisioning, the phone typically does not know the installationId of the IoT device. The provisioning process typically goes like this:

  1. Phone creates a restricted session (with blank installationId ) for the device.
  2. IoT device acts as a Wi-Fi software access point. Phone passes this newly-created session's token, along with the Wi-Fi password, to the IoT device.
  3. IoT device connects to Internet via Wi-Fi, saves its ParseInstallation object.
  4. IoT device calls the following endpoint to associate the its installationId with its session. This endpoint only works with session tokens from restricted sessions. Please note that REST API calls from an IoT device should use the Client Key, not the REST API Key.
curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Client-Key: ${CLIENT_KEY} "  \ -H "X-Parse-Session-Token: r:aVrtljyb7E8xKo9256gfvp4n2"  \ -H "X-Parse-Installation-Id: 2d3777a5-f5fc-4caf-80be-73c766235afb"  \ -H "Content-Type: application/json"  \ -d '{}'  \ https://api.parse.com/ 1 /sessions/me 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/sessions/me' , json.dumps({ }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "r:aVrtljyb7E8xKo9256gfvp4n2" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Security

ParseSession objects can only be accessed by the user specified in the user field. All ParseSession objects have an ACL that is read and write by that user only. You cannot change this ACL. This means querying for sessions will only return objects that match the current logged-in user.

When you log in a user via /1/login , Parse will automatically create a new unrestricted ParseSession object in the Parse Cloud. Same for signups and Facebook/Twitter logins.

Session objects manually created from POST /1/sessions are always restricted. You cannot manually create an unrestricted sessions using the object creation API.

Restricted sessions are prohibited from creating, modifying, or deleting any data in the ParseUser , ParseSession , and ParseRole classes. Restricted session also cannot read unrestricted sessions. Restricted Sessions are useful for "Parse for IoT" devices (e.g Arduino or Embedded C) that may run in a less-trusted physical environment than mobile apps. However, please keep in mind that restricted sessions can still read data on ParseUser , ParseSession , and ParseRole classes, and can read/write data in any other class just like a normal session. So it is still important for IoT devices to be in a safe physical environment and ideally use encrypted storage to store the session token.

If you want to prevent restricted Sessions from modifying classes other than ParseUser , ParseSession , or ParseRole , you can write a Cloud Code beforeSave handler for that class:

Parse.Cloud.beforeSave( "MyClass" , function (request, response) { Parse.Session.current().then( function (session) { if  (session.get( 'restricted' )) { response.error( 'write operation not allowed' ); } response.success(); }); }); 

You can configure Class-Level Permissions (CLPs) for the Session class just like other classes on Parse. CLPs restrict reading/writing of sessions via the /1/sessions API, but do not restrict Parse Cloud's automatic session creation/deletion when users log in, sign up, and log out. We recommend that you disable all CLPs not needed by your app. Here are some common use cases for Session CLPs:

  • Find , Delete — Useful for building a UI screen that allows users to see their active session on all devices, and log out of sessions on other devices. If your app does not have this feature, you should disable these permissions.
  • Create — Useful for "Parse for IoT" apps (e.g. Arduino or Embedded C) that provision restricted user sessions for other devices from the phone app. You should disable this permission when building apps for mobile and web. For "Parse for IoT" apps, you should check whether your IoT device actually needs to access user-specific data. If not, then your IoT device does not need a user session, and you should disable this permission.
  • Get , Update , Add Field — Unless you need these operations, you should disable these permissions.
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Roles

As your app grows in scope and user-base, you may find yourself needing more coarse-grained control over access to pieces of your data than user-linked ACLs can provide. To address this requirement, Parse supports a form of Role-based Access Control . Roles provide a logical way of grouping users with common access privileges to your Parse data. Roles are named objects that contain users and other roles. Any permission granted to a role is implicitly granted to its users as well as to the users of any roles that it contains.

For example, in your application with curated content, you may have a number of users that are considered "Moderators" and can modify and delete content created by other users. You may also have a set of users that are "Administrators" and are allowed all of the same privileges as Moderators, but can also modify the global settings for the application. By adding users to these roles, you can ensure that new users can be made moderators or administrators, without having to manually grant permission to every resource for each user.

We provide a specialized role class to represent these groupings of users for the purposes of assigning permissions. Roles have a few special fields that set them apart from other objects.

  • name: The name for the role. This value is required, and can only be set once as a role is being created. The name must consist of alphanumeric characters, spaces, -, or _. This name will be used to identify the Role without needing its objectId.
  • users: A relation to the set of users that will inherit permissions granted to the containing role.
  • roles: A relation to the set of child roles whose users and roles will inherit permissions granted to the containing role.

Often, in order to keep these roles secure, your mobile apps won't be directly responsible for managing creation and membership of your roles. Instead, roles may be managed by a separate interface on the web or manually managed by an administrator. Our REST API allows you to manage your roles without requiring a mobile client.

Creating Roles

Creating a new role differs from creating a generic object in that the name field is required. Roles must also specify an ACL , which should be as restrictive as possible to avoid allowing the wrong users to modify a role.

To create a new role, send a POST request to the roles root:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "name" : "Moderators" , "ACL" : { "*" : { "read" : true  } } }' \ https: //api.parse.com/1/roles 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/roles' , json.dumps({ "name" : "Moderators" , "ACL" : { "*" : { "read" : True } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

You can create a role with child roles or users by adding existing objects to the roles and users relations :

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "name" : "Moderators" , "ACL" : { "*" : { "read" : true  } }, "roles" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "Ed1nuqPvc"  } ] }, "users" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_User" , "objectId" : "8TOXdXf3tz"  } ] } }' \ https: //api.parse.com/1/roles 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/roles' , json.dumps({ "name" : "Moderators" , "ACL" : { "*" : { "read" : True } }, "roles" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "Ed1nuqPvc"  } ] }, "users" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_User" , "objectId" : "8TOXdXf3tz"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

When the creation is successful, the HTTP response is a 201 Created and the Location header contains the object URL for the new object:

Status : 201 Created Location : https://api.parse.com/1/roles/mrmBZvsErB 

The response body is a JSON object containing the objectId and createdAt timestamp of the newly-created object:

{ " createdAt ": "2012-04-28T17:41:09.106Z" , " objectId ": "mrmBZvsErB" } 

Retrieving Roles

You can also retrieve the contents of a role object by sending a GET request to the URL returned in the location header when it was created. For example, to retrieve the role created above:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /roles/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/roles/mrmBZvsErB' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing all of the fields on the role:

{ " createdAt ": "2012-04-28T17:41:09.106Z" , " objectId ": "mrmBZvsErB" , " updatedAt ": "2012-04-28T17:41:09.106Z" , " ACL ": { " * ": { " read ": true } , " role:Administrators ": { " write ": true } } , " name ": "Moderators" } 

Note that the users and roles relations will not be visible in this JSON. Instead, you must query for the roles and users that belong to a given role using the $relatedTo operator.

Updating Roles

Updating a role generally works like updating any other object , but the name field on the role cannot be changed. Adding and removing users and roles to the users and roles relations can be accomplished by using the AddRelation and RemoveRelation operators.

For example, we can add two users to the "Moderators" role created above like so:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "users" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_User" , "objectId" : "8TOXdXf3tz"  }, { "__type" : "Pointer" , "className" : "_User" , "objectId" : "g7y9tkhB7O"  } ] } }' \ https: //api.parse.com/1/roles/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/roles/mrmBZvsErB' , json.dumps({ "users" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_User" , "objectId" : "8TOXdXf3tz"  }, { "__type" : "Pointer" , "className" : "_User" , "objectId" : "g7y9tkhB7O"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Similarly, we can remove a child role from the "Moderators" role created above like so:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "roles" : { "__op" : "RemoveRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "Ed1nuqPvc"  } ] } }' \ https: //api.parse.com/1/roles/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/roles/mrmBZvsErB' , json.dumps({ "roles" : { "__op" : "RemoveRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "Ed1nuqPvc"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Note that we've included the master key in the query above because the "Moderators" role has an ACL that restricts modification by the public.

Deleting Roles

To delete a role from the Parse Cloud, send a DELETE request to its URL. For example:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ https://api.parse.com/ 1 /roles/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/roles/mrmBZvsErB' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Again, we pass the master key in order to bypass the ACL on the role itself. Alternatively, we could pass an X-Parse-Session-Token for a user that has write access to the Role object (e.g. an Administrator). For example:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "X-Parse-Session-Token: pnktnjyb996sj4p156gjtp4im"  \ https://api.parse.com/ 1 /roles/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/roles/mrmBZvsErB' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "X-Parse-Session-Token" : "pnktnjyb996sj4p156gjtp4im"  }) result = json.loads(connection.getresponse().read()) print  result 

Security

When you access Parse via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify ACLs via the REST API, just by accessing the "ACL" key of an object. In addition to per-user permissions as described above , you can also specify role-level permissions to your Parse objects. Instead of specifying an objectId as the key for a permission object as you do for users, you can instead specify a role's name with a "role:" prefix as the key for a permission object. You can use role-level permissions alongside user-level permissions to provide fine-grained control over user access.

For example, to restrict an object to be readable by anyone in the "Members" role and writable by its creator and anyone in the "Moderators" role, you would specify an ACL like this:

{ " 8TOXdXf3tz ": { " write ": true } , " role:Members ": { " read ": true } , " role:Moderators ": { " write ": true } } 

You are not required to specify read permissions for the user or the "Moderators" role if the user and role are already children of the "Members" role, since they will inherit read permissions granted to "Members".

Role Hierarchy

As described above, one role can contain another, establishing a parent-child relationship between the two roles. The consequence of this relationship is that any permission granted to the parent role is implicitly granted to all of its child roles.

These types of relationships are commonly found in applications with user-managed content, such as forums. Some small subset of users are "Administrators", with the highest level of access to tweaking the application's settings, creating new forums, setting global messages, and so on. Another set of users are "Moderators", who are responsible for ensuring that the content created by users remains appropriate. Any user with Administrator privileges should also be granted the permissions of any Moderator. To establish this relationship, you would make your "Administrators" role a child role of "Moderators" by adding the "Administrators" role to the roles relation on your "Moderators" object like this:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "roles" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "<AdministratorsRoleObjectId>"  } ] } }' \ https: //api.parse.com/1/roles/<ModeratorsRoleObjectId> 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/roles/<ModeratorsRoleObjectId>' , json.dumps({ "roles" : { "__op" : "AddRelation" , "objects" : [ { "__type" : "Pointer" , "className" : "_Role" , "objectId" : "<AdministratorsRoleObjectId>"  } ] } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Files

Uploading Files

To upload a file to Parse, send a POST request to the files URL, postfixed with the name of the file. The request must contain the Content-Type header associated with the file. Keep in mind that files are limited to 10 megabytes. Here's a simple example that'll create a file named hello.txt containing a string:

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: text/plain"  \ -d 'Hello, World!'  \ https://api.parse.com/ 1 /files/hello.txt 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/files/hello.txt' , 'Hello, World!' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "text/plain"  }) result = json.loads(connection.getresponse().read()) print  result 

When the file upload is successful, the HTTP response is a 201 Created and the Location header which contains the URL for the file:

Status : 201 Created Location : http://files.parsetfss.com/bc9f32df-2957-4bb1-93c9-ec47d9870a05/tfss-db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt 

The response body is a JSON object containing the name of the file, which is the original file name prefixed with a unique identifier in order to prevent name collisions. This means you can save files with the same name, and the files will not overwrite one another.

{ " url ": "http://files.parsetfss.com/bc9f32df-2957-4bb1-93c9-ec47d9870a05/tfss-db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt" , " name ": "db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt" } 

To upload an image, the syntax is a little bit different. Here's an example that will upload the image myPicture.jpg from the current directory.

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: image/jpeg"  \ --data-binary '@myPicture.jpg'  \ https://api.parse.com/ 1 /files/pic.jpg 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/files/pic.jpg' , open( 'myPicture.jpg' , 'rb' ).read(), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "image/jpeg"  }) result = json.loads(connection.getresponse().read()) print  result 

Associating with Objects

After files are uploaded, you can associate them with Parse objects:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "name" : "Andrew" , "picture" : { "name" : "...profile.png" , "__type" : "File"  } }' \ https: //api.parse.com/1/classes/PlayerProfile 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/classes/PlayerProfile' , json.dumps({ "name" : "Andrew" , "picture" : { "name" : "...profile.png" , "__type" : "File"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Note that the name of the file in the request is not the local file name, but the name in the response of the previous upload operation.

Deleting Files

Users holding the master key are allowed to delete files using the REST API. To delete a file, send a DELETE request to the files URL, postfixed with the name of the file. Note that the name of the file must be the name in the response of the upload operation, rather than the original filename. Note that the X-Parse-Master-Key must be provided in headers.

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ https://api.parse.com/ 1 /files/...profile.png 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/files/...profile.png' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Note that deleting a PFObject with a file associated with it will not delete the file. All files stored on Parse should be deleted by using the above explained API.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

GeoPoints

Parse allows you to associate real-world latitude and longitude coordinates with an object. Adding a GeoPoint data type to a class allows queries to take into account the proximity of an object to a reference point. This allows you to easily do things like find out what user is closest to another user or which places are closest to a user.

GeoPoint

To associate a point with an object you will need to embed a GeoPoint data type into your object. This is done by using a JSON object with __type set to the string GeoPoint and numeric values being set for the latitude and longitude keys. For example, to create an object containing a point under the "location" key with a latitude of 40.0 degrees and -30.0 degrees longitude:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "location" : { "__type" : "GeoPoint" , "latitude" : 40.0 , "longitude" : - 30.0  } }' \ https: //api.parse.com/1/classes/PlaceObject 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/classes/PlaceObject' , json.dumps({ "location" : { "__type" : "GeoPoint" , "latitude" : 40.0 , "longitude" : - 30.0  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Geo Queries

Now that you have a bunch of objects with spatial coordinates, it would be nice to find out which objects are closest to a point. This can be done by using a GeoPoint data type with query on the field using $nearSphere . Getting a list of ten places that are closest to a user may look something like:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode 'limit= 10 ' \ --data-urlencode ' where ={ "location" : { "$nearSphere" : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  } } }' \ https: //api.parse.com/1/classes/PlaceObject 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "limit" : 10 , "where" :json.dumps({ "location" : { " $nearSphere " : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  } } })}) connection.connect() connection.request( 'GET' , '/1/classes/PlaceObject?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

This will return a list of results ordered by distance from 30.0 latitude and -20.0 longitude. The first result will be the nearest object. (Note that if an explicit order parameter is supplied, it will take precedence over the distance ordering.) For example, here are two results returned for the above query:

{ " results ": [ { " location ": { " latitude ": 40.0 , " __type ": "GeoPoint" , " longitude ": - 30.0 } , " updatedAt ": "2011-12-06T22:36:04.983Z" , " createdAt ": "2011-12-06T22:36:04.983Z" , " objectId ": "iFEPN5Gwoz" }, { " location ": { " latitude ": 60.0 , " __type ": "GeoPoint" , " longitude ": - 20.0 } , " updatedAt ": "2011-12-06T22:36:26.143Z" , " createdAt ": "2011-12-06T22:36:26.143Z" , " objectId ": "LAyNKSNTHT" } ] } 

To limit the search to a maximum distance add a $maxDistanceInMiles (for miles), $maxDistanceInKilometers (for kms), or $maxDistanceInRadians (for radian angle), term to the key constraint. For example, the following limits the radius to 10 miles:

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "location" : { "$nearSphere" : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  }, "$maxDistanceInMiles" : 10.0  } }' \ https: //api.parse.com/1/classes/PlaceObject 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "location" : { " $nearSphere " : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  }, " $maxDistanceInMiles " : 10.0  } })}) connection.connect() connection.request( 'GET' , '/1/classes/PlaceObject?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

It's also possible to query for the set of objects that are contained within a particular area. To find the objects in a rectangular bounding box, add a clause to the key constraint with the format {"$within": {"$box": {[southwestGeoPoint, northeastGeoPoint]}}} .

curl - X GET  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - G  \ --data-urlencode ' where ={ "location" : { "$within" : { "$box" : [ { "__type" : "GeoPoint" , "latitude" : 37.71 , "longitude" : - 122.53  }, { "__type" : "GeoPoint" , "latitude" : 30.82 , "longitude" : - 122.37  } ] } } }' \ https: //api.parse.com/1/classes/PizzaPlaceObject 
import json,httplib,urllib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) params = urllib.urlencode({ "where" :json.dumps({ "location" : { " $within " : { " $box " : [ { "__type" : "GeoPoint" , "latitude" : 37.71 , "longitude" : - 122.53  }, { "__type" : "GeoPoint" , "latitude" : 30.82 , "longitude" : - 122.37  } ] } } })}) connection.connect() connection.request( 'GET' , '/1/classes/PizzaPlaceObject?%s'  % params, '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Caveats

At the moment there are a couple of things to watch out for:

  1. Each PFObject class may only have one key with a PFGeoPoint object.
  2. Using the $nearSphere constraint will also limit results to within 100 miles.
  3. Points should not equal or exceed the extreme ends of the ranges. Latitude should not be -90.0 or 90.0. Longitude should not be -180.0 or 180.0. Attempting to use GeoPoint 's with latitude and/or longitude outside these ranges will cause an error.
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Push Notification

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Installations

Uploading Installation Data

An installation object represents an instance of your app being installed on a device. These objects are used to store subscription data for installations which have subscribed to one or more push notification channels. Installations have a flexible schema, except that the special fields below have special type and validation requirements:

  • badge : is a number field representing the last known application badge for iOS installations.
  • channels : An array of the channels to which a device is currently subscribed.
  • timeZone : The current time zone where the target device is located. This should be an IANA time zone identifier .
  • deviceType : The type of device, "ios", "android", "winrt", "winphone", or "dotnet" (readonly) .
  • pushType : This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly) .
  • GCMSenderId : This field only has meaning for Android installations that use the GCM push type. It is reserved for directing Parse to send pushes to this installation with an alternate GCM sender ID. This field should generally not be set unless you are uploading installation data from another push provider. If you set this field, then you must set the GCM API key corresponding to this GCM sender ID in your Parse application's push settings.
  • installationId : Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app's installations. (readonly) .
  • deviceToken : The Apple or Google generated token used to deliver messages to the APNs or GCM push networks respectively.
  • channelUris : The Microsoft-generated push URIs for Windows devices.
  • appName : The display name of the client application to which this installation belongs.
  • appVersion : The version string of the client application to which this installation belongs.
  • parseVersion : The version of the Parse SDK which this installation uses.
  • appIdentifier : A unique identifier for this installation's client application. In iOS, this is the Bundle Identifier.

Most of the time, installation data is modified by push-related methods in the client SDK. For example, calling subscribeToChannel or unsubscribeFromChannel from the client SDK will create an object for that installation if it doesn't yet exist and update its channels, and calling getSubscribedChannels from the client SDK will read subscription data from that installation's object. The REST methods can be used to mimic these operations. For instance, if you have an iOS device token then you can subscribe it to push notifications by creating an installation object for it with the desired channels list. You can also perform operations which aren't possible through the client SDK, like using a query over installations to find the set of subscribers to a given channel.

Creating an installation object is similar to creating a generic object, but the special installation fields listed above must pass validation. For example, if you have a device token provided by the Apple Push Notification service and would like to subscribe it to the broadcast channel "" , you can use the following command:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "deviceType" : "ios" , "deviceToken" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , "channels" : [ ""  ] }' \ https: //api.parse.com/1/installations 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/installations' , json.dumps({ "deviceType" : "ios" , "deviceToken" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , "channels" : [ ""  ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

When the creation is successful, the HTTP response is a 201 Created and the Location header contains the URL for the new installation:

Status : 201 Created Location : https://api.parse.com/1/installations/mrmBZvsErB 

The response body is a JSON object containing the objectId and the createdAt timestamp of the newly-created installation:

{ " createdAt ": "2012-04-28T17:41:09.106Z" , " objectId ": "mrmBZvsErB" } 

When creating Android installation objects containing GCM (Google Cloud Messaging) credentials, you must have at least the following fields in your installation object:

  • A deviceType set to android .
  • A pushType set to gcm .
  • A GCM registration ID in the deviceToken field.
  • The GCM sender ID associated with this registration ID in the GCMSenderId field.

You could create and object with these fields using a command like this:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "deviceType" : "android" , "pushType" : "gcm" , "deviceToken" : "APA91bFMvbrGg4cp3KUV_7dhU1gmwE_..." , "GCMSenderId" : "56712320625545" , "channels" : [ ""  ] }' \ https: //api.parse.com/1/installations 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/installations' , json.dumps({ "deviceType" : "android" , "pushType" : "gcm" , "deviceToken" : "APA91bFMvbrGg4cp3KUV_7dhU1gmwE_..." , "GCMSenderId" : "56712320625545" , "channels" : [ ""  ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

If you upload Android installations with GCM credentials, then you must also set the GCM API Key associated with this GCM sender ID in your application's push settings.

Retrieving Installations

You can retrieve the contents of an installation object by sending a GET request to the URL returned in the location header when it was created. For example, to retrieve the installation created above:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /installations/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/installations/mrmBZvsErB' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing all the user-provided fields, plus the createdAt , updatedAt , and objectId fields:

{ " deviceType ": "ios" , " deviceToken ": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , " channels ": [ ""  ] , " createdAt ": "2012-04-28T17:41:09.106Z" , " updatedAt ": "2012-04-28T17:41:09.106Z" , " objectId ": "mrmBZvsErB" } 

Updating Installations

Installation objects can be updated by sending a PUT request to the installation URL. For example, to subscribe the installation above to the "foo" push channel:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "deviceType" : "ios" , "deviceToken" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , "channels" : [ "" , "foo"  ] }' \ https: //api.parse.com/1/installations/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/installations/mrmBZvsErB' , json.dumps({ "deviceType" : "ios" , "deviceToken" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , "channels" : [ "" , "foo"  ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Note that there is a restriction on updating the deviceToken field of Installation objects. You can only update the deviceToken field of an Installation object if contains a non-nil installationId field.

Querying Installations

You can retrieve multiple installations at once by sending a GET request to the root installations URL. This functionality is not available in the SDKs, so you must authenticate this method using the X-Parse-Master-Key header in your request instead of the X-Parse-REST-API-Key header. Your master key allows you to bypass ACLs and should only be used from within a trusted environment.

Without any URL parameters, a GET request simply lists installations:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ https://api.parse.com/ 1 /installations 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/installations' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The return value is a JSON object that contains a results field with a JSON array that lists the users.

{ " results ": [ { " deviceType ": "ios" , " deviceToken ": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" , " channels ": [ ""  ] , " createdAt ": "2012-04-28T17:41:09.106Z" , " updatedAt ": "2012-04-28T17:41:09.106Z" , " objectId ": "mrmBZvsErB" }, { " deviceType ": "ios" , " deviceToken ": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" , " channels ": [ ""  ] , " createdAt ": "2012-04-30T01:52:57.975Z" , " updatedAt ": "2012-04-30T01:52:57.975Z" , " objectId ": "sGlvypFQcO" } ] } 

All of the options for queries that work for regular objects also work for installation objects, so check the section on Querying Objects for more details. By doing an array query over channels , for example, you can find the set of devices subscribed to a given push channel.

Deleting Installations

To delete an installation from the Parse Cloud, send a DELETE request to its URL. This functionality is not available in the client SDKs, so you must authenticate this method using the X-Parse-Master-Key header in your request instead of the X-Parse-REST-API-Key header. Your master key allows you to bypass ACLs and should only be used from within a trusted environment. For example:

curl -X DELETE \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ https://api.parse.com/ 1 /installations/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'DELETE' , '/1/installations/mrmBZvsErB' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting . Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

A channel is identified by a string that starts with a letter and consists of alphanumeric characters, underscores, and dashes. It doesn't need to be explicitly created before it can be used and each Installation can subscribe to any number of channels at a time.

Subscribing to a channel via the REST API can be done by updating the Installation object. We send a PUT request to the Installation URL and update the channels field. For example, in a baseball score app, we could do:

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ "channels": [ "Giants" ] }'  \ https://api.parse.com/ 1 /installations/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/installations/mrmBZvsErB' , json.dumps({ "channels" : [ "Giants"  ] }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Once subscribed to the "Giants" channel, your Installation object should have an updated channels field.

To unsubscribe from a channel you would need to update the channels array and remove the unsubscribed channel.

Sending Pushes to Channels

With the REST API, the following code can be used to alert all subscribers of the "Giants" and "Mets" channels about the results of the game. This will display a notification center alert to iOS users and a system tray notification to Android users.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "channels" : [ "Giants" , "Mets"  ], "data" : { "alert" : "The Giants won against the Mets 2-3."  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "channels" : [ "Giants" , "Mets"  ], "data" : { "alert" : "The Giants won against the Mets 2-3."  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since Installation objects are just like any other object stored in Parse, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

Storing arbitrary data on an Installation object is done in the same way we store data on any other object on Parse. In our Baseball app, we could allow users to get pushes about game results, scores and injury reports.

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "scores" : true , "gameResults" : true , "injuryReports" : true  }' \ https: //api.parse.com/1/installations/mrmBZvsErB 
import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/installations/mrmBZvsErB' , json.dumps({ "scores" : True , "gameResults" : True , "injuryReports" : True  }), { "X-Parse-Application-Id" : "${APPLICATION_ID}" , "X-Parse-REST-API-Key" : "${REST_API_KEY}" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

You can even create relationships between your Installation objects and other classes saved on Parse. To associate an Installation with a particular user, for example, you can use a pointer to the _User class on the Installation .

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "user" : { "__type" : "Pointer" , "className" : "_User" , "objectId" : "vmRZXZ1Dvo"  } }' \ https: //api.parse.com/1/installations/mrmBZvsErB 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/installations/mrmBZvsErB' , json.dumps({ "user" : { "__type" : "Pointer" , "className" : "_User" , "objectId" : "vmRZXZ1Dvo"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a query to target a subset of these devices. Installation queries work just like any other Parse query .

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "injuryReports" : true  }, "data" : { "alert" : "Willie Hayes injured by own pop fly."  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "injuryReports" : True }, "data" : { "alert" : "Willie Hayes injured by own pop fly."  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "channels" : "Giants" , "scores" : true  }, "data" : { "alert" : "The Giants scored a run! The score is now 2-2."  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "channels" : "Giants" , "scores" : True }, "data" : { "alert" : "The Giants scored a run! The score is now 2-2."  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "user" : { "$inQuery" : { "location" : { "$nearSphere" : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  }, "$maxDistanceInMiles" : 1.0  } } } }, "data" : { "alert" : "Free hotdogs at the Parse concession stand!"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "user" : { " $inQuery " : { "location" : { " $nearSphere " : { "__type" : "GeoPoint" , "latitude" : 30.0 , "longitude" : - 20.0  }, " $maxDistanceInMiles " : 1.0  } } } }, "data" : { "alert" : "Free hotdogs at the Parse concession stand!"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

An in depth look at the Installation end point can be found in the REST guide .

Sending Options

Push notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. In Android, it is even possible to specify an Intent to be fired upon receipt of a notification. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you can set other fields in the data dictionary. There are some reserved fields that have a special meaning.

  • alert : the notification's message.
  • badge : (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound : (iOS only) the name of a sound file in the application bundle.
  • content-available : (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category : (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri : (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title : (Android only) the value displayed in the Android system tray notification.

For example, to send a notification that increases the current badge number by 1 and plays a custom sound for iOS devices, and displays a particular title for Android users, you can do the following:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "channels" : [ "Mets"  ], "data" : { "alert" : "The Mets scored! The game is now tied 1-1." , "badge" : "Increment" , "sound" : "cheering.caf" , "title" : "Mets Score!"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "channels" : [ "Mets"  ], "data" : { "alert" : "The Mets scored! The game is now tied 1-1." , "badge" : "Increment" , "sound" : "cheering.caf" , "title" : "Mets Score!"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

It is also possible to specify your own data in this dictionary. As explained in the Receiving Notifications section for iOS and Android , iOS will give you access to this data only when the user opens your app via the notification and Android will provide you this data in the Intent if one is specified.

`` ` ` `` python import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "channels" : [ "Indians"  ], "data" : { "action" : "com.example.UPDATE_STATUS" , "alert" : "Ricky Vaughn was injured during the game last night!" , "name" : "Vaughn" , "newsItem" : "Man bites dog"  } }), { "X-Parse-Application-Id" : "${APPLICATION_ID}" , "X-Parse-REST-API-Key" : "${REST_API_KEY}" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two parameters provided by Parse to allow setting an expiration date for your notification. The first is expiration_time which takes a date (in ISO 8601 format or Unix epoch time) specifying when Parse should stop trying to send the notification. To expire the notification exactly 1 week from now, you can use the following command.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "expiration_time" : "2015-03-19T22:05:08Z" , "data" : { "alert" : "Season tickets on sale until March 19, 2015"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "expiration_time" : "2015-03-19T22:05:08Z" , "data" : { "alert" : "Season tickets on sale until March 19, 2015"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Alternatively, you can use the expiration_interval parameter to specify a duration of time before your notification expired. This value is relative to the push_time parameter used to schedule notifications . This means that a push notification scheduled to be sent out in 1 day and an expiration interval of 6 days can be received up to a week from March 16th, 2015.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "push_time" : "2015-03-13T22:05:08Z" , "expiration_interval" : 518400 , "data" : { "alert" : "Season tickets on sale until March 19, 2015"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "push_time" : "2015-03-13T22:05:08Z" , "expiration_interval" : 518400 , "data" : { "alert" : "Season tickets on sale until March 19, 2015"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target iOS or Android devices. There are two methods provided to filter which of these devices are targeted. Note that both platforms are targeted by default.

The following examples would send a different notification to Android, iOS, and Windows users.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "deviceType" : "android"  }, "data" : { "alert" : "Your suitcase has been filled with tiny robots!"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "deviceType" : "android"  }, "data" : { "alert" : "Your suitcase has been filled with tiny robots!"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "deviceType" : "ios"  }, "data" : { "alert" : "Your suitcase has been filled with tiny apples!"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "deviceType" : "ios"  }, "data" : { "alert" : "Your suitcase has been filled with tiny apples!"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "deviceType" : "winrt"  }, "data" : { "alert" : "Your suitcase has been filled with tiny glass!"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "deviceType" : "winrt"  }, "data" : { "alert" : "Your suitcase has been filled with tiny glass!"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "deviceType" : "winphone"  }, "data" : { "alert" : "Your suitcase is very hip; very metro."  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "deviceType" : "winphone"  }, "data" : { "alert" : "Your suitcase is very hip; very metro."  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Scheduling Pushes

You can schedule a push in advance by specifying a push_time . For example, if a user schedules a game reminder for a game on March 19th, 2015 at noon UTC, you can schedule the push notification by sending:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "where" : { "user_id" : "user_123"  }, "push_time" : "2015-03-19T12:00:00Z" , "data" : { "alert" : "You previously created a reminder for the game today"  } }' \ https: //api.parse.com/1/push 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/push' , json.dumps({ "where" : { "user_id" : "user_123"  }, "push_time" : "2015-03-19T12:00:00Z" , "data" : { "alert" : "You previously created a reminder for the game today"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

If you also specify an expiration_interval , it will be calculated from the scheduled push time, not from the time the push is submitted. This means a push scheduled to be sent in a week with an expiration interval of a day will expire 8 days after the request is sent.

The scheduled time cannot be in the past, and can be up to two weeks in the future. It can be an ISO 8601 date with a date, time, and timezone, as in the example above, or it can be a numeric value representing a UNIX epoch time in seconds (UTC). To schedule an alert for 08/22/2015 at noon UTC time, you can set the push_time to either 2015-08-022T12:00:00.000Z or 1440226800000 .

Local Push Scheduling

The push_time parameter can schedule a push to be delivered to each device according to its time zone. This technique delivers a push to all Installation objects with a timeZone member when that time zone would match the push time. For example, if an app had a device in timezone America/New_York and another in America/Los_Angeles , the first would receive the push three hours before the latter.

To schedule a push according to each device's local time, the push_time parameter should be an ISO 8601 date without a time zone, i.e. 2015-03-19T12:00:00 . Note that Installations without a timeZone will be excluded from this localized push.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Config

Parse Config is a way to configure your applications remotely by storing a single configuration object on Parse. It enables you to add things like feature gating or a simple "Message of the day". To start using Parse Config you need to add a few key/value pairs (parameters) to your app on the Parse Config Dashboard.

After that you will be able to fetch the config on the client by sending a GET request to config URL. Here is a simple example that will fetch the Parse.Config :

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ https://api.parse.com/ 1 /config 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/config' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} "  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is a JSON object containing all the configuration parameters in the params field.

{ " params ": { " welcomeMessage ": "Welcome to The Internet!" , " winningNumber ": 42 } } 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Analytics

Parse provides a number of hooks for you to get a glimpse into the ticking heart of your app. We understand that it's important to understand what your app is doing, how frequently, and when.

While this section will cover different ways to instrument your app to best take advantage of Parse's analytics backend, developers using Parse to store and retrieve data can already take advantage of metrics on Parse.

Without having to implement any client-side logic, you can view real-time graphs and breakdowns (by device type, Parse class name, or REST verb) of your API Requests in your app's dashboard and save these graph filters to quickly access just the data you're interested in.

The current server time will be used for all analytics requests. To explicitly set the time associated with a given event, an optional at parameter can be provided in ISO 8601 format.

-d '{ "at" : { "__type" : "Date" , "iso" : "2015-03-01T15:59:11-07:00"  } } 

App-Open Analytics

Our analytics hook allows you to track your application being launched. By making a POST request to our REST API, you'll begin to collect data on when and how often your application is opened.

In the example below, the at parameter is optional. If omitted, the current server time will be used instead.

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ }'  \ https://api.parse.com/ 1 /events/AppOpened 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/events/AppOpened' , json.dumps({ }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Graphs and breakdowns of your statistics are accessible from your app's Dashboard.

Custom Analytics

Parse Analytics also allows you to track free-form events, with a handful of string keys and values. These extra dimensions allow segmentation of your custom events via your app's Dashboard.

Say your app offers search functionality for apartment listings, and you want to track how often the feature is used, with some additional metadata.

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-REST-API-Key: ${REST_API_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "dimensions" : { "priceRange" : "1000-1500" , "source" : "craigslist" , "dayType" : "weekday"  } }' \ https: //api.parse.com/1/events/Search 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/events/Search' , json.dumps({ "dimensions" : { "priceRange" : "1000-1500" , "source" : "craigslist" , "dayType" : "weekday"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Parse Analytics can even be used as a lightweight error tracker — simply invoke the following and you'll have access to an overview of the rate and frequency of errors, broken down by error code, in your application:

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ "dimensions": { "code": "404" } }'  \ https://api.parse.com/ 1 /events/Error 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/events/Error' , json.dumps({ "dimensions" : { "code" : "404"  } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Note that Parse currently only stores the first eight dimension pairs per call to /1/events/<eventName> .

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Cloud Code

Cloud Functions

Cloud Functions can be called using the REST API. For example, to call the Cloud Function named hello :

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-REST-API-Key: ${REST_API_KEY} "  \ -H "Content-Type: application/json"  \ -d '{}'  \ https://api.parse.com/ 1 / functions /hello 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/functions/hello' , json.dumps({ }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-REST-API-Key" : " ${REST_API_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Background Jobs

Similarly, you can trigger a background job from the REST API. For example, to trigger the job named userMigration :

Take a look at the Cloud Code Guide to learn more about Cloud Functions and Background Jobs.
curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"plan":"paid"}'  \ https://api.parse.com/ 1 / jobs /userMigration 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/jobs/userMigration' , json.dumps({ "plan" : "paid"  }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Schema

Schema is the structure representing classes in your app. You can use the schema of an app to verify operations in a unit test, generate test data, generate test classes and then clean up after tests. The schema API can also be used to create custom views of your data. We use the schema API to display columns names and types in the databrowser.

This API allows you to access the schemas of your app. Note: This API can be only accessed using the master key .

Fetch the schema

To fetch the Schema for all the classes of your app, run:

Note: createdAt and updatedAt are of type Date but they are represented as strings in object representation. This is a special case for the Parse API.

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /schemas 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/schemas' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is JSON containing all the schema information of the app.

{ " results ": [ { " className ": "Game" , " fields ": { " ACL ": { " type ": "ACL" } , " createdAt ": { " type ": "Date" } , " objectId ": { " type ": "String" } , " name ": { " type ": "String" } , " score ": { " type ": "Number" } , " updatedAt ": { " type ": "Date" } } }, ... ] } 

To fetch schema of a single class, run:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /schemas/Game 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/schemas/Game' , "" , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Adding a schema

When you add a new schema to your app, it creates an empty class with the provided fields and some default fields applicable to the class. To add the schema, run:

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d ' { "className" : "City" , "fields" : { "name" : { "type" : "String"  } } }' \ https: //api.parse.com/1/schemas/City 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/schemas/Game' , json.dumps({ "className" : "City" , "fields" :{ "name" :{ "type" : "String" } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Modifying the schema

You can add or delete columns to a schema. To do so, run:

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d ' { "className" : "City" , "fields" : { "population" : { "type" : "Number"  } } }' \ https: //api.parse.com/1/schemas/City 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/schemas/City' , json.dumps( "className" : "City" , "fields" :{ "population" :{ "type" : "Number" } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

To delete a particular field, you need to use {"__op" : "Delete" }

curl - X PUT  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d ' { "className" : "City" , "fields" : { "population" : { "__op" : "Delete"  } } }' \ https: //api.parse.com/1/schemas/City 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/schemas/Game' , json.dumps( "className" : "City" , "fields" :{ "population" :{ "__op"  : "Delete" } } }), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Removing a schema

You can only remove a schema from your app if it is empty (has 0 objects). To do that, run:

curl -X DELETE\ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /schemas/City 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/schemas/City' , "" , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Apps

You can view, create, and edit your Parse apps via the REST API, in addition to through your parse.com dashboard. By authenticating with your Parse account's email and password, you can fetch all the app keys for each of the apps that you are a collaborator on, create a new app that you own, or update the settings of an app that you are a collaborator on. This API can be used to create test apps to run in automated tests, and in combination with the schema API can be used to programmatically replicate your existing app into a test app.

Authentication for the apps endpoint is done a little differently than the rest of the REST API. Instead of authenticating with one of your app's keys, you must use your account's login info. The X-Parse-Email header identifies which account you are using, and the X-Parse-Password header authenticates the endpoint. If your account has no password set, because you used Facebook, GitHub, or Google to log in, then go to your account page to set a password. Alternatively, you can also generate a Parse account key and use the header X-Parse-Account-Key instead.

Fetching apps

To fetch the keys and settings for all of the apps that you are a collaborator on, run:

curl -X GET \ -H "X-Parse-Email: <PARSE_ACCOUNT_EMAIL>"  \ -H "X-Parse-Password: <PARSE_ACCOUNT_PASSWORD>"  \ -H "Content-Type: application/json"  \ https: //api.parse.com/1/apps 
import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/apps' , '' , { "X-Parse-Email" : "<PARSE_ACCOUNT_EMAIL>" , "X-Parse-Password" : "<PARSE_ACCOUNT_PASSWORD>gt;" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The response body is JSON containing the keys and settings for your apps.

{ " results ": [ { " appName ": "<APPLICATION_NAME>" , " applicationId ": "<APPLICATION_ID>" , " clientClassCreationEnabled ": true , " clientPushEnabled ": false , " dashboardURL ": "https://www.parse.com/apps/yourapp" , " javascriptKey ": "<JAVASCRIPT_KEY>" , " masterKey ": "<MASTER_KEY>" , " requireRevocableSessions ": true , " restKey ": "<REST_API_KEY>" , " revokeSessionOnPasswordChange ": true , " webhookKey ": "<WEBHOOK_KEY>" , " windowsKey ": "<WINDOWS_KEY>" }, ... ] } 

To fetch the keys and settings of a single app, run:

curl - X GET  \ - H "X-Parse-Email: <PARSE_ACCOUNT_EMAIL>"  \ - H "X-Parse-Password: <PARSE_ACCOUNT_PASSWORD>"  \ - H "Content-Type: application/json"  \ https: / /api.parse.com/ 1 /apps/ ${ APPLICATION_ID } 
import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/apps/${APPLICATION_ID}' , '' , { "X-Parse-Email" : "<PARSE_ACCOUNT_EMAIL>" , "X-Parse-Password" : "<PARSE_ACCOUNT_PASSWORD>" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Creating apps

By sending a POST request to the /1/apps endpoint you can create a new app, that is owned by your account. The new app is initialized with a set of keys, as well as some optional settings, which are all returned to you along with the app's URL in your parse.com dashboard. The only required field for creating an app is the app name.

The default values for the allowable settings are:

App Setting Default Value
clientClassCreationEnabled true
clientPushEnabled false
requireRevocableSessions true
revokeSessionOnPasswordChange true
curl - X POST  \ - H "X-Parse-Email: <PARSE_ACCOUNT_EMAIL>"  \ - H "X-Parse-Password: <PARSE_ACCOUNT_PASSWORD>"  \ - H "Content-Type: application/json"  \ -d '{ "appName" : "my new app" , "clientClassCreationEnabled" : false }' \ https: //api.parse.com/1/apps 
import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/apps' , json.dumps( "appName" : "my new app" , "clientClassCreationEnabled" : false  }), { "X-Parse-Email" : "<PARSE_ACCOUNT_EMAIL>" , "X-Parse-Password" : "<PARSE_ACCOUNT_PASSWORD>" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

Updating apps

You can change your app's name, as well as change your app's settings, by sending a PUT request to /1/apps :

curl - X PUT  \ - H "X-Parse-Email: ${PARSE_ACCOUNT_EMAIL}"  \ - H "X-Parse-Password: <PARSE_ACCOUNT_PASSWORD>"  \ - H "Content-Type: application/json"  \ -d '{ "appName" : "updated app name" , "clientClassCreationEnabled" : true }' \ https: //api.parse.com/1/apps/${APPLICATION_ID} 
import  json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/apps/${APPLICATION_ID}' , json.dumps( "appName" : "updated app name" , "clientClassCreationEnabled" : true  }), { "X-Parse-Email" : "<PARSE_ACCOUNT_EMAIL>" , "X-Parse-Password" : "<PARSE_ACCOUNT_PASSWORD>" , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Hooks

You can create, update, list or delete all your cloud code webhooks via the Hooks API, in addition to being able to do so through the parse website.

Hooks API requires the users to provide Application-Id and Master-Key in the request headers.

There are two kinds of cloud code webhooks: function webhooks and trigger webhooks.

Cloud functions are functions that run in the cloud and allow you to build functions common to all platforms. For more details please read cloud code functions .

Cloud triggers are invoked whenever you save or delete a parse object. Triggers are commonly used to validate your objects before saving them. For more details please read cloud code triggers .

Lets establish some basic terminology used throughout the rest of this section.

Cloud Code Webhooks consist of function webhooks and trigger webhooks . This is code that runs on your servers.

Cloud Code has cloud code functions and cloud code triggers . This is code that runs on the Parse servers.

These are the generic concepts encapsulating both use cases:

Cloud Function is either a cloud code function or a function webhook . Cloud Trigger is either a cloud code trigger or a trigger webhook .

A function webhook has a name and a url. Hence, its JSON response looks like:

{" functionName ": "foo" , " url ": "https://api.example.com/foo" } 

JSON reponse for a cloud code function just contains the function name.

{" functionName ": "foo" } 

A trigger webhook belongs to a class, has a trigger name and a url. Hence, its JSON response looks like:

{" className ": "score" , " triggerName ": "beforeSave" , " url ": "https://api.example.com/score/beforeSave" } 

JSON response for a cloud code trigger contains the class name and the trigger name.

{" className ": "score" , " triggerName ": "beforeSave" } 

Note that trigger name can only be one of beforeSave , afterSave , beforeDelete and afterDelete .

Fetch functions

To fetch the list of all cloud functions you deployed or created, use:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /hooks/ functions 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/hooks/functions' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output is a json object with one key: "results" whose value is a list of cloud functions.

{ " results ": [ { " functionName ": "sendMessage" , " url ": "https://api.example.com/sendMessage" }, { " functionName ": "sendMessage" }, { " functionName ": "foo" , " url ": "https://api.example.com/foo" }, { " functionName ": "bar" } ] } 

To fetch a single cloud function with a given name, use:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /hooks/ functions /sendMessage 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/hooks/functions/sendMessage' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output is a json object with one key: "results" whose value is a list of cloud functions with the given name.

{ " results ": [ { " functionName ": "sendMessage" , " url ": "https://api.example.com/sendMessage" }, { " functionName ": "sendMessage" } ] } 

Fetch triggers

To fetch the list of all cloud triggers you deployed or created, use:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /hooks/triggers 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/hooks/triggers' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output is a json object with one key: "results" whose value is a list of cloud triggers.

{ " results ": [ { " className ": "Scores" , " triggerName ": "beforeSave" }, { " className ": "Scores" , " triggerName ": "beforeSave" , " url ": "https://api.example.com/Scores/beforeSave" }, { " className ": "Game" , " triggerName ": "afterSave" , " url ": "https://api.example.com/Game/afterSave" }, { " className ": "Tournament" , " triggerName ": "beforeDelete" } ] } 

To fetch a single cloud trigger, use:

curl -X GET \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ https://api.parse.com/ 1 /hooks/triggers/Scores/beforeSave 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'GET' , '/1/hooks/triggers/Scores/beforeSave' , '' , { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The path looks like /hooks/triggers/className/triggerName where triggerName can be one of beforeSave , afterSave , beforeDelete , afterDelete .

The output may look like this:

{ " results ": [ { " className ": "Scores" , " triggerName ": "beforeSave" }, { " className ": "Scores" , " triggerName ": "beforeSave" , " url ": "https://api.example.com/Scores/beforeSave" } ] } 

The output is a json object with one key: "results" whose value is a list of all cloud triggers with given name for a given class.

Note that POST, PUT and DELETE only work on function or trigger webhooks. To create cloud code functions or cloud code triggers you can modify your cloud code javascript files and perform a parse deploy the usual way.

Create function webhook

To create a new function webhook post to /1/hooks/functions with payload in the format

{" functionName " : x , " url " : y } 

Post example,

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"functionName":"baz","url":"https://api.example.com/baz"}'  \ https://api.parse.com/ 1 /hooks/ functions 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/hooks/functions' , json.dumps( { "functionName" : "baz" , "url" : "https://api.example.com/baz" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ "functionName" : "baz" , "url" : "https://api.example.com/baz" } ' 

It returns the function name and url of the created webhook.

If you try to create a function webhook and a cloud code function with the same name already exists, upon successful creation the response json has an additional warning field informing about the name conflict. Note that, function webhooks takes precedence over cloud code functions.

For example,

curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"functionName":"bar","url":"https://api.example.com/bar"}'  \ https://api.parse.com/ 1 /hooks/ functions 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/hooks/functions' , json.dumps( { "functionName" : "bar" , "url" : "https://api.example.com/bar" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " functionName ": "bar" , " url ": "https://api.example.com/bar" , " warning ": "a cloudcode function with name: bar already exists" } 

Create trigger webhook

To create a new function webhook post to /1/hooks/triggers with payload in the format

{" className ": x , " triggerName ": y , " url ": z } 
{" className ": x , " triggerName ": y , " url ": z } 

Post example,

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "className" : "Game" , "triggerName" : "beforeSave" , "url" : "https://api.example.com/Game/beforeSave" }' \ https: //api.parse.com/1/hooks/triggers 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/hooks/triggers' , json.dumps( { "className" : "Game" , "triggerName" : "beforeSave" , "url" : "https://api.example.com/Game/beforeSave" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " className ": "Game" , " triggerName ": "beforeSave" , " url ": "https://api.example.com/Game/beforeSave" } 

It returns the class name, trigger name and url of the created trigger webhook.

If you try to create a trigger webhook and a cloud code trigger with the same name already exists, upon successful creation the response json has an additional warning field informing about the name conflict. Note that, trigger webhooks takes precedence over cloud code triggers.

For example,

curl - X POST  \ - H "X-Parse-Application-Id: ${APPLICATION_ID}"  \ - H "X-Parse-Master-Key: ${MASTER_KEY}"  \ - H "Content-Type: application/json"  \ -d '{ "className" : "Tournament" , "triggerName" : "beforeDelete" , "url" : "https://api.example.com/Scores/beforeDelete" }' \ https: //api.parse.com/1/hooks/triggers 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'POST' , '/1/hooks/triggers' , json.dumps( { "className" : "Tournament" , "triggerName" : "beforeDelete" , "url" : "https://api.example.com/Scores/beforeDelete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " className ": "Tournament" , " triggerName ": "beforeDelete" , " url ": "https://api.example.com/Tournament/beforeDelete" , " warning ": "beforeDelete trigger already defined for class Tournament in cloud code" } 

Edit function webhook

To edit the url of a function webhook that was already created use the put method.

Put example,

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"url":"https://api.example.com/_baz"}'  \ https://api.parse.com/ 1 /hooks/ functions /baz 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/functions/baz' , json.dumps( { "url" : "https://api.example.com/_baz" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ "functionName" : "baz" , "url" : "https://api.example.com/baz" } ' 

It returns the function name and url of the modified webhook.

If you try to update a function webhook and a cloud code function with the same name already exists, upon successful update the response json has an additional warning field informing about the name conflict. Note that, function webhooks takes precedence over cloud code functions.

For example,

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"url":"https://api.example.com/_bar"}'  \ https://api.parse.com/ 1 /hooks/ functions /bar 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/functions/bar' , json.dumps( { "url" : "https://api.example.com/_bar" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " functionName ": "bar" , " url ": "https://api.example.com/_bar" , " warning ": "a cloudcode function with name: bar already exists" } 

Edit trigger webhook

To edit the url of a trigger webhook that was already crated use the put method.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"url": "https://api.example.com/Game/_beforeSave"}'  \ https://api.parse.com/ 1 /hooks/triggers/Game/beforeSave 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/triggers/Game/beforeSave' , json.dumps( { "url" : "https://api.example.com/Game/_beforeSave" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " className ": "Game" , " triggerName ": "beforeSave" , " url ": "https://api.example.com/Game/_beforeSave" } 

It returns the class name, trigger name and url of the modified trigger webhook.

If you try to update a trigger webhook and a cloud code trigger with the same name already exists, upon successful update the response json has an additional warning field informing about the name conflict. Note that, trigger webhooks takes precedence over cloud code triggers.

For example,

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"url": "https://api.example.com/Scores/beforeDelete"}'  \ https://api.parse.com/ 1 /hooks/triggers/Tournament/beforeDelete 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/triggers/Tournament/beforeDelete' , json.dumps( { "url" : "https://api.example.com/Scores/beforeDelete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " className ": "Tournament" , " triggerName ": "beforeDelete" , " url ": "https://api.example.com/Tournament/beforeDelete" , " warning ": "beforeDelete trigger already defined for class Tournament in cloud code" } 

Delete function webhook

To delete a function webhook use the put method.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{"__op": "Delete"}'  \ https://api.parse.com/ 1 /hooks/ functions /foo 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/functions/foo' , json.dumps( { "__op" : "Delete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ } 

If a cloud code function with the same name already exists then it is returned as the result. Since the overriding webhook was just deleted, this cloud code function will be run the next time sendMessage is called.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ "__op": "Delete" }'  \ https://api.parse.com/ 1 /hooks/ functions /sendMessage 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/functions/sendMessage' , json.dumps( { "__op" : "Delete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " functionName ": "sendMessage" } 

Delete trigger webhook

To delete a trigger webhook use the put method.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ "__op": "Delete" }'  \ https://api.parse.com/ 1 /hooks/triggers/Game/beforeSave 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/triggers/Game/beforeSave' , json.dumps( { "__op" : "Delete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ } 

If a cloud code trigger with the same name already exists then the it is returned as the result. Since the overriding webhook was just deleted, this cloud code trigger will be run the next time a Tournament object is saved.

curl -X PUT \ -H "X-Parse-Application-Id: ${APPLICATION_ID} "  \ -H "X-Parse-Master-Key: ${MASTER_KEY} "  \ -H "Content-Type: application/json"  \ -d '{ "__op": "Delete" }'  \ https://api.parse.com/ 1 /hooks/triggers/Tournament/beforeDelete 
import json,httplib connection = httplib.HTTPSConnection( 'api.parse.com' , 443 ) connection.connect() connection.request( 'PUT' , '/1/hooks/triggers/Tournament/beforeDelete' , json.dumps( { "__op" : "Delete" } ), { "X-Parse-Application-Id" : " ${APPLICATION_ID} " , "X-Parse-Master-Key" : " ${MASTER_KEY} " , "Content-Type" : "application/json"  }) result = json.loads(connection.getresponse().read()) print  result 

The output may look like this:

{ " className ": "Tournament" , " triggerName ": "beforeDelete" } 
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Data

We've designed the Parse SDKs so that you typically don't need to worry about how data is saved while using the client SDKs. Simply add data to the ParseObject , and it'll be saved correctly.

Nevertheless, there are some cases where it's useful to be aware of how data is stored on the Parse platform.

Data Storage

Internally, Parse stores data as JSON, so any datatype that can be converted to JSON can be stored on Parse. Refer to the Data Types in Objects section of this guide to see platform-specific examples.

Keys including the characters $ or . , along with the key __type key, are reserved for the framework to handle additional types, so don't use those yourself. Key names must contain only numbers, letters, and underscore, and must start with a letter. Values can be anything that can be JSON-encoded.

Data Type Lock-in

When a class is initially created, it doesn't have an inherent schema defined. This means that for the first object, it could have any types of fields you want.

However, after a field has been set at least once, that field is locked into the particular type that was saved. For example, if a ParseUser object is saved with field name of type String , that field will be restricted to the String type only (the server will return an error if you try to save anything else).

One special case is that any field can be set to null , no matter what type it is.

The Data Browser

The Data Browser is the web UI where you can update and create objects in each of your apps. Here, you can see the raw JSON values that are saved that represents each object in your class.

When using the interface, keep in mind the following:

  • The objectId , createdAt , updatedAt fields cannot be edited (these are set automatically).
  • The value "(empty)" denotes that the field has not been set for that particular object (this is different than null ).
  • You can remove a field's value by hitting your Delete key while the value is selected.

The Data Browser is also a great place to test the Cloud Code validations contained in your Cloud Code functions (such as beforeSave ). These are run whenever a value is changed or object is deleted from the Data Browser, just as they would be if the value was changed or deleted from your client code.

Importing Data

You may import data into your Parse app by using CSV or JSON files. To create a new class with data from a CSV or JSON file, go to the Data Browser and click the "Import" button on the left hand column.

The JSON format is an array of objects in our REST format or a JSON object with a results that contains an array of objects. It must adhere to the JSON standard . A file containing regular objects could look like:

{ " results ": [ { " score ": 1337 , " playerName ": "Sean Plott" , " cheatMode ": false , " createdAt ": "2012-07-11T20:56:12.347Z" , " updatedAt ": "2012-07-11T20:56:12.347Z" , " objectId ": "fchpZwSuGG" }] } 

Objects in either format should contain keys and values that also satisfy the following:

  • Key names must contain only numbers, letters, and underscore, and must start with a letter.
  • No value may contain a hard newline ' \n '.

Normally, when objects are saved to Parse, they are automatically assigned a unique identifier through the objectId field, as well as a createdAt field and updatedAt field which represent the time that the object was created and last modified in the Parse Cloud. These fields can be manually set when data is imported from a JSON file. Please keep in mind the following:

  • Use a unique 10 character alphanumeric string as the value of your objectId fields.
  • Use a UTC timestamp in the ISO 8601 format when setting a value for the createdAt field or the updatedAt field.

In addition to the exposed fields, objects in the Parse User class can also have the bcryptPassword field set. The value of this field is a String that is the bcrypt hashed password + salt in the modular crypt format described in this StackOverflow answer . Most OpenSSL based bcrypt implementations should have built-in methods to produce these strings.

A file containing a ParseUser object could look like:

{ " results ": [{ " username ": "cooldude" , " createdAt ": "1983-09-13T22:42:30.548Z" , " updatedAt ": "2015-09-04T10:12:42.137Z" , " objectId ": "ttttSEpfXm" , " sessionToken ": "dfwfq3dh0zwe5y2sqv514p4ib" , " bcryptPassword ": "$2a$10$ICV5UeEf3lICfnE9W9pN9.O9Ved/ozNo7G83Qbdk5rmyvY8l16MIK" }] } 

Note that in CSV the import field types are limited to String , Boolean , and Number .

Exporting your Data

You can request an export of your data at any time from your app's Settings page. The data export runs at a lower priority than production queries, so if your app is still serving queries, production traffic will always be given a higher priority, which may slow down the delivery of your data export.

Export Formats

Each collection will be exported in the same JSON format used by our REST API and delivered in a single zipped file. Since data is stored internally as JSON, this allows us to ensure that the export closely matches how the data is saved to Parse. Other formats such as CSV cannot represent all of the data types supported by Parse without losing information. If you'd like to work with your data in CSV format, you can use any of the JSON-to-CSV converters available widely on the web.

Offline Analysis

For offline analysis of your data, we highly recommend using alternate ways to access your data that do not require extracting the entire collection at once. For example, you can try exporting only the data that has changed since your last export. Here are some ways of achieving this:

  • Use the JavaScript SDK in a node app. Parse.Query.each() will allow you to extract every single object that matches a query. You can use date constraints to make sure the query only matches data that has been updated since you last ran this app. Your node app can write this data to disk for offline analysis.

  • Use the REST API in a script. You can run queries against your class and use skip/limit to page through results, which can then be written to disk for offline analysis. You can again use date constraints to make sure only newly updated data is extracted.

  • If the above two options do not fit your needs, you can try using the Data Browser to export data selectively. Use the Funnel icon to create a filter for the specific data that you need to export, such as newly updated objects. Once the filter has been applied, click on the Export data icon on the upper right of your Data Browser. This type of export will only include the objects that match your criteria.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Relations

There are three kinds of relationships. One-to-one relationships enable one object to be associated with another object. One-to-many relationships enable one object to have many related objects. Finally, many-to-many relationships enable complex relationships among many objects.

There are four ways to build relationships in Parse:

One-to-Many

When you’re thinking about one-to-many relationships and whether to implement Pointers or Arrays, there are several factors to consider. First, how many objects are involved in this relationship? If the "many" side of the relationship could contain a very large number (greater than 100 or so) of objects, then you have to use Pointers. If the number of objects is small (fewer than 100 or so), then Arrays may be more convenient, especially if you typically need to get all of the related objects (the "many" in the "one-to-many relationship") at the same time as the parent object.

Using Pointers

Let's say we have a game app. The game keeps track of the player's score and achievements every time she chooses to play. In Parse, we can store this data in a single Game object. If the game becomes incredibly successful, each player will store thousands of Game objects in the system. For circumstances like this, where the number of relationships can be arbitrarily large, Pointers are the best option.

Suppose in this game app, we want to make sure that every Game object is associated with a Parse User. We can implement this like so:

ParseObject game = new  ParseObject( "Game" ); game.put( "createdBy" , ParseUser.getCurrentUser()); 
PFObject *game= [PFObject objectWithClassName:@ "Game" ]; [game set Object:[PFUser currentUser] for Key:@ "createdBy" ]; 
let  game = PFObject(className: "Game" ) game[ "createdBy" ] = PFUser.currentUser() 
$game  = ParseObject::create( "Game" ); $game -> set ( "createdBy" , ParseUser::getCurrentUser()); 
var  game = new  ParseObject( "Game" ); game[ "createdBy" ] = ParseUser.CurrentUser; 
var  game = new  Parse.Object( "Game" ); game. set ( "createdBy" , Parse.User.current()); 
# No REST API example 
// No C++ example 

We can obtain all of the Game objects created by a Parse User with a query:

ParseQuery<ParseObject> gameQuery = ParseQuery.getQuery( "Game" ); gameQuery.whereEqualTo( "createdBy" , ParseUser.getCurrentUser()); 
PFQuery *gameQuery = [PFQuery queryWithClassName:@ "Game" ]; [gameQuery where Key:@ "createdBy"  equalTo:[PFUser currentUser]]; 
let  gameQuery = PFQuery(className: "Game" ) if let  user = PFUser. currentUser () { gameQuery.whereKey( "createdBy" , equalTo: user) } 
$gameQuery  = new  ParseQuery( "Game" ); $gameQuery ->equalTo( "createdBy" , ParseUser::getCurrentUser()); 
var  query = ParseObject.getQuery( "Game" ).WhereEqualTo( "createdBy" , ParseUser.CurrentUser); 
var  query = new  Parse.Query( "Game" ); query.equalTo( "createdBy" , Parse.User.current()); 
# No REST API example 
// No C++ example 

And, if we want to find the Parse User who created a specific Game , that is a lookup on the createdBy key:

// say we have a Game object  ParseObject game = ... // getting the user who created the Game  ParseUser createdBy = game.getUser( "createdBy" ); 
// say we have a Game object  PFObject *game = ... // getting the user who created the Game  PFUser *createdBy = [game objectForKey@ "createdBy" ]; 
// say we have a Game object let  game = ... // getting the user who created the Game let  createdBy = game[ "createdBy" ] 
// say we have a Game object $game  = ... // getting the user who created the Game $user  = $game [ "createdBy" ]; 
// say we have a Game object  ParseObject game = ... // getting the user who created the Game  ParseUser user = game[ "createdBy" ]; 
// say we have a Game object var  game = ... // getting the user who created the Game var  user = game. get ( "createdBy" ); 
# No REST API example 
// No C++ example 

For most scenarios, Pointers will be your best bet for implementing one-to-many relationships.

Using Arrays

Arrays are ideal when we know that the number of objects involved in our one-to-many relationship are going to be small. Arrays will also provide some productivity benefit via the includeKey parameter. Supplying the parameter will enable you to obtain all of the "many" objects in the "one-to-many" relationship at the same time that you obtain the "one" object. However, the response time will be slower if the number of objects involved in the relationship turns out to be large.

Suppose in our game, we enabled players to keep track of all the weapons their character has accumulated as they play, and there can only be a dozen or so weapons. In this example, we know that the number of weapons is not going to be very large. We also want to enable the player to specify the order in which the weapons will appear on screen. Arrays are ideal here because the size of the array is going to be small and because we also want to preserve the order the user has set each time they play the game:

Let's start by creating a column on our Parse User object called weaponsList .

Now let's store some Weapon objects in the weaponsList :

// let's say we have four weapons  ParseObject scimitar = ... ParseObject plasmaRifle = ... ParseObject grenade = ... ParseObject bunnyRabbit = ... // stick the objects in an array  ArrayList<ParseObject> weapons = new  ArrayList<ParseObject>(); weapons.add(scimitar); weapons.add(plasmaRifle); weapons.add(grenade); weapons.add(bunnyRabbit); // store the weapons for the user  ParseUser.getCurrentUser().put( "weaponsList" , weapons); 
// let's say we have four weapons  PFObject *scimitar = ... PFObject *plasmaRifle = ... PFObject *grenade = ... PFObject *bunnyRabbit = ... // stick the objects in an array  NSArray *weapons = @[scimitar, plasmaRifle, grenade, bunnyRabbit]; // store the weapons for the user  [[PFUser currentUser] setObject:weapons forKey: @weaponsList "]; 
// let's say we have four weapons let  scimitar = ... let  plasmaRifle = ... let  grenade = ... let  bunnyRabbit = ... // stick the objects in an array let  weapons = [scimitar, plasmaRifle, grenade, bunnyRabbit] // store the weapons for the user let  user = PFUser.currentUser() user[ "weaponsList" ] = weapons 
// let's say we have four weapons $scimitar  = ... $plasmaRifle  = ... $grenade  = ... $bunnyRabbit  = ... // stick the objects in an array $weapons  = [ $scimitar , $plasmaRifle , $grenade , $bunnyRabbit ]; // store the weapons for the user $user  = ParseUser::getCurrentUser(); $user ->set( "weaponsList" , weapons); 
// let's say we have four weapons var  scimitar = ... var  plasmaRifle = ... var  grenade = ... var  bunnyRabbit = ... // stick the objects in an array var  weapons = new List <ParseObject>(); weapons.Add(scimitar); weapons.Add(plasmaRifle); weapons.Add(grenade); weapons.Add(bunnyRabbit); // store the weapons for the user var  user = ParseUser.CurrentUser; user.AddRangeToList( "weaponsList" , weapons); 
// let's say we have four weapons var  scimitar = ... var  plasmaRifle = ... var  grenade = ... var  bunnyRabbit = ... // stick the objects in an array var  weapons = [scimitar, plasmaRifle, grenade, bunnyRabbit]; // store the weapons for the user var  user = Parse.User.current(); user. set ( "weaponsList" , weapons); 
# No REST API example 
// No C++ example 

Later, if we want to retrieve the Weapon objects, it's just one line of code:

ArrayList<ParseObject> weapons = ParseUser.getCurrentUser(). get ( "weaponsList" ); 
NSArray  *weapons = [[PFUser currentUser] objectForKey: @"weaponsList" ]; 
let  weapons = PFUser.currentUser()?.objectForKey( "weaponsList" ) 
$weapons  = ParseUser::getCurrentUser()->get( "weaponsList" ); 
var  weapons = ParseUser.CurrentUser.Get<IList< Object >>( "weaponsList" ); 
var  weapons = Parse.User.current(). get ( "weaponsList" ) 
# No REST API example 
// No C++ example 

Sometimes, we will want to fetch the "many" objects in our one-to-many relationship at the same time as we fetch the "one" object. One trick we could employ is to use the includeKey (or include in Android) parameter whenever we use a Parse Query to also fetch the array of Weapon objects (stored in the weaponsList column) along with the Parse User object:

// set up our query for a User object  ParseQuery<ParseUser> userQuery = ParseUser.getQuery(); // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one"  userQuery.include( "weaponsList" ); // execute the query  userQuery.findInBackground( new  FindCallback<ParseUser>() { public void done (List<ParseUser> userList, ParseException e) { // userList contains all of the User objects, and their associated Weapon objects, too  } }); 
// set up our query for a User object  PFQuery *userQuery = [PFUser query]; // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one"  [userQuery includeKey: @"weaponsList" ]; // execute the query  [userQuery findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { // objects contains all of the User objects, and their associated Weapon objects, too  }]; 
// set up our query for a User object let  userQuery = PFUser.query(); // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one"  userQuery?.includeKey( "weaponsList" ); // execute the query  userQuery?.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in // objects contains all of the User objects, and their associated Weapon objects, too  } 
// set up our query for a User object $userQuery  = ParseUser::query(); // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one" $userQuery ->includeKey( "weaponsList" ); // execute the query $results  = $userQuery ->find(); // results contains all of the User objects, and their associated Weapon objects, too 
// set up our query for a User object var  userQuery = ParseUser.Query; // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one"  userQuery = userQuery.Include( "weaponsList" ); // execute the query  IEnumerable<ParseUser> results = await  userQuery.FindAsync(); // results contains all of the User objects, and their associated Weapon objects, too 
// set up our query for a User object var  userQuery = new  Parse.Query(Parse.User); // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one"  userQuery. include ( "weaponsList" ); // execute the query  userQuery.find({ success: function (results) { // results contains all of the User objects, and their associated Weapon objects, too  } }); 
# No REST API example 
// No C++ example 

You can also get the "one" side of the one-to-many relationship from the "many" side. For example, if we want to find all Parse User objects who also have a given Weapon , we can write a constraint for our query like this:

// add a constraint to query for whenever a specific Weapon is in an array  userQuery.whereEqualTo( "weaponsList" , scimitar); // or query using an array of Weapon objects...  userQuery.whereEqualTo( "weaponsList" , arrayOfWeapons); 
// add a constraint to query for whenever a specific Weapon is in an array  [userQuery whereKey:@ "weaponsList"  equalTo:scimitar]; // or query using an array of Weapon objects...  [userQuery whereKey:@ "weaponsList"  containedIn:arrayOfWeapons]; 
// add a constraint to query for whenever a specific Weapon is in an array  userQuery?.whereKey( "weaponsList" , equalTo: scimitar); // or query using an array of Weapon objects...  userQuery?.whereKey( "weaponsList" , containedIn: arrayOfWeapons) 
// add a constraint to query for whenever a specific Weapon is in an array $userQuery ->equalTo( "weaponsList" , $scimitar ); // or query using an array of Weapon objects... $userQuery ->containedIn( "weaponsList" , $arrayOfWeapons ); 
// add a constraint to query for whenever a specific Weapon is in an array  userQuery = userQuery.WhereEqualTo( "weaponsList" , scimitar); // or query using an array of Weapon objects...  userQuery = userQuery.WhereContainedIn( "weaponsList" , arrayOfWeapons); 
// add a constraint to query for whenever a specific Weapon is in an array  userQuery.equalTo( "weaponsList" , scimitar); // or query using an array of Weapon objects...  userQuery.containedIn( "weaponsList" , arrayOfWeapons); 
# No REST API example 
// No C++ example 

Many-to-Many

Now let’s tackle many-to-many relationships. Suppose we had a book reading app and we wanted to model Book objects and Author objects. As we know, a given author can write many books, and a given book can have multiple authors. This is a many-to-many relationship scenario where you have to choose between Arrays, Parse Relations, or creating your own Join Table.

The decision point here is whether you want to attach any metadata to the relationship between two entities. If you don’t, Parse Relation or using Arrays are going to be the easiest alternatives. In general, using arrays will lead to higher performance and require fewer queries. If either side of the many-to-many relationship could lead to an array with more than 100 or so objects, then, for the same reason Pointers were better for one-to-many relationships, Parse Relation or Join Tables will be better alternatives.

On the other hand, if you want to attach metadata to the relationship, then create a separate table (the "Join Table") to house both ends of the relationship. Remember, this is information about the relationship , not about the objects on either side of the relationship. Some examples of metadata you may be interested in, which would necessitate a Join Table approach, include:

Using Parse Relations

Using Parse Relations, we can create a relationship between a Book and a few Author objects. In the Data Browser, you can create a column on the Book object of type relation and name it authors .

After that, we can associate a few authors with this book:

// let’s say we have a few objects representing Author objects  ParseObject authorOne = ParseObject authorTwo = ParseObject authorThree = // now we create a book object  ParseObject book = new  ParseObject( "Book" ); // now let’s associate the authors with the book // remember, we created a "authors" relation on Book  ParseRelation<ParseObject> relation = book.getRelation( "authors" ); relation.add(authorOne); relation.add(authorTwo); relation.add(authorThree); // now save the book object  book.saveInBackground(); 
// let’s say we have a few objects representing Author objects  PFObject *authorOne = … PFObject *authorTwo = … PFObject *authorThree = … // now we create a book object  PFObject *book= [PFObject objectWithClassName:@ "Book" ]; // now let’s associate the authors with the book // remember, we created a "authors" relation on Book  PFRelation *relation = [book relationForKey:@ "authors" ]; [relation addObject:authorOne]; [relation addObject:authorTwo]; [relation addObject:authorThree]; // now save the book object  [book saveInBackground]; 
// let’s say we have a few objects representing Author objects let  authorOne = ... let  authorTwo = ... let  authorThree = ... // now we create a book object let  book = PFObject(className: "Book" ) // now let’s associate the authors with the book // remember, we created a "authors" relation on Book let  relation = book.relationForKey( "authors" ) relation.addObject(authorOne) relation.addObject(authorTwo) relation.addObject(authorThree) // now save the book object  book.saveInBackground() 
// let’s say we have a few objects representing Author objects $authorOne  = ... $authorTwo  = ... $authorThree  = ... // now we create a book object $book  = new  ParseObject( "Book" ); // now let’s associate the authors with the book // remember, we created a "authors" relation on Book $relation  = $book ->getRelation( "authors" ); $relation ->add( $authorOne ); $relation ->add( $authorTwo ); $relation ->add( $authorThree ); // now save the book object $book ->save(); 
// let’s say we have a few objects representing Author objects var  authorOne = ... var  authorTwo = ... var  authorThree = ... // now we create a book object var  book = new  ParseObject( "Book" ); // now let’s associate the authors with the book // remember, we created a "authors" relation on Book var  relation = book.GetRelation<ParseObject>( "authors" ); relation.Add(authorOne); relation.Add(authorTwo); relation.Add(authorThree); // now save the book object await  book.SaveAsync(); 
// let’s say we have a few objects representing Author objects var  authorOne = ... var  authorTwo = ... var  authorThree = ... // now we create a book object var  book = new  Parse.Object( "Book" ); // now let’s associate the authors with the book // remember, we created a "authors" relation on Book var  relation = book.relation( "authors" ); relation.add(authorOne); relation.add(authorTwo); relation.add(authorThree); // now save the book object  book.save(); 
# No REST API example 
// No C++ example 

To get the list of authors who wrote a book, create a query:

// suppose we have a book object  ParseObject book = ... // create a relation based on the authors key  ParseRelation relation = book.getRelation( "authors" ); // generate a query based on that relation  ParseQuery query = relation.getQuery(); // now execute the query 
// suppose we have a book object  PFObject *book = ... // create a relation based on the authors key  PFRelation *relation = [book relationForKey:@ "authors" ]; // generate a query based on that relation  PFQuery *query = [relation query]; // now execute the query 
// suppose we have a book object let  book = ... // create a relation based on the authors key let  relation = book.relationForKey( "authors" ) // generate a query based on that relation let  query = relation.query() // now execute the query 
// suppose we have a book object $book  = ... // create a relation based on the authors key $relation  = $book ->getRelation( "authors" ); // generate a query based on that relation $query  = $relation ->getQuery(); // now execute the query 
// suppose we have a book object var  book = ... // create a relation based on the authors key var  relation = book.GetRelation<ParseObject>( "authors" ); // generate a query based on that relation var  query = relation.Query; // now execute the query 
// suppose we have a book object var  book = ... // create a relation based on the authors key var  relation = book.relation( "authors" ); // generate a query based on that relation var  query = relation.query(); // now execute the query 
# No REST API example 
// No C++ example 

Perhaps you even want to get a list of all the books to which an author contributed. You can create a slightly different kind of query to get the inverse of the relationship:

// suppose we have a author object, for which we want to get all books  ParseObject author = ... // first we will create a query on the Book object  ParseQuery<ParseObject> query = ParseQuery.getQuery( "Book" ); // now we will query the authors relation to see if the author object we have // is contained therein  query.whereEqualTo( "authors" , author); 
// suppose we have a author object, for which we want to get all books  PFObject *author = ... // first we will create a query on the Book object  PFQuery *query = [PFQuery queryWithClassName:@ "Book" ]; // now we will query the authors relation to see if the author object // we have is contained therein  [query whereKey:@ "authors"  equalTo:author]; 
// suppose we have a author object, for which we want to get all books let  author = ... // first we will create a query on the Book object let  query = PFQuery(className: "Book" ) // now we will query the authors relation to see if the author object // we have is contained therein  query?.whereKey( "authors" , equalTo: author) 
// suppose we have a author object, for which we want to get all books $author  = ... // first we will create a query on the Book object $query  = new  ParseQuery( "Book" ); // now we will query the authors relation to see if the author object we have // is contained therein $query ->equalTo( "authors" , $author ); 
// suppose we have a author object, for which we want to get all books var  author = ... // first we will create a query on the Book object var  query = ParseObject.GetQuery( "Book" ); // now we will query the authors relation to see if the author object we have // is contained therein  query = query.WhereEqualTo( "authors" , author); 
// suppose we have a author object, for which we want to get all books var  author = ... // first we will create a query on the Book object var  query = new  Parse.Query( "Book" ); // now we will query the authors relation to see if the author object we have // is contained therein  query.equalTo( "authors" , author); 
# No REST API example 
// No C++ example 

Using Join Tables

There may be certain cases where we want to know more about a relationship. For example, suppose we were modeling a following/follower relationship between users: a given user can follow another user, much as they would in popular social networks. In our app, we not only want to know if User A is following User B, but we also want to know when User A started following User B. This information could not be contained in a Parse Relation. In order to keep track of this data, you must create a separate table in which the relationship is tracked. This table, which we will call Follow , would have a from column and a to column, each with a pointer to a Parse User. Alongside the relationship, you can also add a column with a Date object named date .

Now, when you want to save the following relationship between two users, create a row in the Follow table, filling in the from , to , and date keys appropriately:

// suppose we have a user we want to follow  ParseUser otherUser = ... // create an entry in the Follow table  ParseObject follow = new  ParseObject( "Follow" ); follow.put( "from" , ParseUser.getCurrentUser()); follow.put( "to" , otherUser); follow.put( "date" , Date ()); follow.saveInBackground(); 
// suppose we have a user we want to follow PFUser *otherUser = ... // create an entry in  the Follow table PFObject *follow = [PFObject objectWithClassName:@ "Follow" ]; [follow set Object:[PFUser currentUser] for Key:@ "from" ]; [follow set Object:otherUser for Key:@ "to" ]; [follow set Object:[NSDate date] for Key@ "date" ]; [follow saveInBackground]; 
// suppose we have a user we want to follow let  otherUser = ... // create an entry in the Follow table let  follow = PFObject(className: "Follow" ) follow.setObject(PFUser.currentUser()!, forKey: "from" ) follow.setObject(otherUser, forKey: "to" ) follow.setObject(NSDate(), forKey: "date" ) follow.saveInBackground() 
// suppose we have a user we want to follow $otherUser  = ... // create an entry in the Follow table $follow  = new  ParseObject( "Follow" ); $follow ->set( "from" , ParseUser::getCurrentUser()); $follow ->set( "to" , $otherUser ); $follow ->set( "date" , new  DateTime()); $follow ->save(); 
// suppose we have a user we want to follow  ParseUser otherUser = ... // create an entry in the Follow table var  follow = new  ParseObject( "Follow" ); follow[ "from" ] = ParseUser.CurrentUser; follow[ "to" ] = otherUser; follow[ "date" ] = DateTime.UtcNow; await  follow.SaveAsync(); 
var  otherUser = ... // create an entry in the Follow table var  follow = new  Parse.Object( "Follow" ); follow. set ( "from" , Parse.User.current()); follow. set ( "to" , otherUser); follow. set ( "date" , Date()); follow.save(); 
# No REST API example 
// No C++ example 

If we want to find all of the people we are following, we can execute a query on the Follow table:

// set up the query on the Follow table  ParseQuery<ParseObject> query = ParseQuery.getQuery( "Follow" ); query.whereEqualTo( "from" , ParseUser.getCurrentUser()); // execute the query  query.findInBackground(newFindCallback<ParseObject>() { public void done (List<ParseObject> followList, ParseException e) { } }); 
// set up the query on the Follow table  PFQuery *query = [PFQuery queryWithClassName: @"Follow" ]; [query whereKey: @"from"  equalTo:[PFUser currentUser]]; // execute the query  [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { for (PFObject *o in  objects) { // o is an entry in the Follow table // to get the user, we get the object with the to key  PFUser *otherUser = [o objectForKey @"to" ]; // to get the time when we followed this user, get the date key  PFObject *when = [o objectForKey @"date" ]; } }]; 
// set up the query on the Follow table let  query = PFQuery(className: "Follow" ) query.whereKey( "from" , equalTo: PFUser.currentUser()!) // execute the query  query.findObjectsInBackgroundWithBlock{ (objects: [AnyObject]?, error: NSError?) -> Void in if let  objects = objects { for  o in  objects { // o is an entry in the Follow table // to get the user, we get the object with the to key let  otherUse = o.objectForKey( "to" ) as ? PFUser // to get the time when we followed this user, get the date key let  when = o.objectForKey( "date" ) as ? PFObject } } } 
// set up the query on the Follow table $query  = new  ParseQuery( "Follow" ); $query ->equalTo( "from" , ParseUser::getCurrentUser()); // execute the query $results  = $query ->find(); 
// set up the query on the Follow table  ParseQuery<ParseObject> query = ParseQuery.getQuery( "Follow" ); query = query.WhereEqualTo( "from" , ParseUser.CurrentUser); // execute the query  IEnumerable<ParseObject> results = await  query.FindAsync(); 
var  query = new  Parse.Query( "Follow" ); query.equalTo( "from" , Parse.User.current()); query.find({ success: function (users) { ... } }); 
# No REST API example 
// No C++ example 

It’s also pretty easy to find all the users that are following the current user by querying on the to key:

// set up the query on the Follow table  ParseQuery<ParseObject> query = ParseQuery.getQuery( "Follow" ); query.whereEqualTo( "to" , ParseUser.getCurrentUser()); // execute the query  query.findInBackground(newFindCallback<ParseObject>() { public void done (List<ParseObject> followList, ParseException e) { } }); 
// set up the query on the Follow table  PFQuery *query = [PFQuery queryWithClassName: @"Follow" ]; [query whereKey: @"to"  equalTo:[PFUser currentUser]]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { for (PFObject *o in  objects) { // o is an entry in the Follow table // to get the user, we get the object with the from key  PFUser *otherUser = [o objectForKey @"from" ]; // to get the time the user was followed, get the date key  PFObject *when = [o objectForKey @"date" ]; } }]; 
// set up the query on the Follow table let  query = PFQuery(className: "Follow" ) query.whereKey( "to" , equalTo: PFUser.currentUser()!) query.findObjectsInBackgroundWithBlock{ (objects: [AnyObject]?, error: NSError?) -> Void in if let  objects = objects { for  o in  objects { // o is an entry in the Follow table // to get the user, we get the object with the to key let  otherUse = o.objectForKey( "to" ) as ? PFUser // to get the time when we followed this user, get the date key let  when = o.objectForKey( "date" ) as ? PFObject } } } 
// create an entry in the Follow table $query  = new  ParseQuery( "Follow" ); $query ->equalTo( "to" , ParseUser::getCurrentUser()); $results  = $query ->find(); 
// create an entry in the Follow table var  query = ParseObject.GetQuery( "Follow" ) .WhereEqualTo( "to" , ParseUser.CurrentUser); IEnumerable<ParseObject> results = await  query.FindAsync(); 
// create an entry in the Follow table var  query = new  Parse.Query( "Follow" ); query.equalTo( "to" , Parse.User.current()); query.find({ success: function (users) { ... } }); 
# No REST API example 
// No C++ example 

Using an Array

Arrays are used in Many-to-Many relationships in much the same way that they are for One-to-Many relationships. All objects on one side of the relationship will have an Array column containing several objects on the other side of the relationship.

Suppose we have a book reading app with Book and Author objects. The Book object will contain an Array of Author objects (with a key named authors ). Arrays are a great fit for this scenario because it's highly unlikely that a book will have more than 100 or so authors. We will put the Array in the Book object for this reason. After all, an author could write more than 100 books.

Here is how we save a relationship between a Book and an Author .

// let's say we have an author  ParseObject author = ... // and let's also say we have an book  ParseObject book = ... // add the author to the authors list for the book  book.put( "authors" , author); 
// let's say we have an author  PFObject *author = ... // and let's also say we have an book  PFObject *book = ... // add the author to the authors list for the book  [book addObject:author forKey:@ "authors" ]; 
// let's say we have an author let  author = ... // and let's also say we have an book let  book = ... // add the author to the authors list for the book  book.addObject(author, forKey: "authors" ) 
// let's say we have an author $author  = ... // and let's also say we have an book $book  = ... // add the author to the authors list for the book $book ->addUnique( "authors" , array ( $author )); 
// let's say we have an author var  author = ... // and let's also say we have an book var  book = ... // add the author to the authors list for the book  book.AddToList( "authors" , author); 
// let's say we have an author var  author = ... // and let's also say we have an book var  book = ... // add the author to the authors list for the book  book.add( "authors" , author); 
# No REST API example 
// No C++ example 

Because the author list is an Array, you should use the includeKey (or include on Android) parameter when fetching a Book so that Parse returns all the authors when it also returns the book:

// set up our query for the Book object  ParseQuery bookQuery = ParseQuery.getQuery( "Book" ); // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book  bookQuery.include( "authors" ); // execute the query  bookQuery.findInBackground(newFindCallback<ParseObject>() { public void done (List<ParseObject> bookList, ParseException e) { } }); 
// set up our query for the Book object  PFQuery *bookQuery = [PFQuery queryWithClassName: @"Book" ]; // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book  [bookQuery includeKey: @"authors" ]; // execute the query  [bookQuery findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { // objects is all of the Book objects, and their associated // Author objects, too  }]; 
// set up our query for the Book object let  bookQuery = PFQuery(className: "Book" ) // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book  bookQuery.includeKey( "authors" ) // execute the query  bookQuery.findObjectsInBackgroundWithBlock{ (objects: [AnyObject]?, error: NSError?) -> Void in // objects is all of the Book objects, and their associated // Author objects, too  } 
// set up our query for the Book object $bookQuery  = new  ParseQuery( "Book" ); // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book $bookQuery ->includeKey( "authors" ); // execute the query $books = $bookQuery ->find(); 
// set up our query for the Book object var  bookQuery = ParseObject.GetQuery( "Book" ); // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book  bookQuery = bookQuery.Include( "authors" ); // execute the query  IEnumerable<ParseObject> books= await  bookQuery.FindAsync(); 
// set up our query for the Book object var  bookQuery = new  Parse.Query( "Book" ); // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book  bookQuery. include ( "authors" ); // execute the query  bookQuery.find({ success: function (books) { ... } }); 
# No REST API example 
// No C++ example 

At that point, getting all the Author objects in a given Book is a pretty straightforward call:

ArrayList<ParseObject> authorList = book.getList( "authors" ); 
NSArray  *authorList = [book objectForKey @"authors" ]; 
let  authorList = book.objectForKey( "authors" ) as ? NSArray 
$authorList  = $book ->get( "authors" ); 
var  authorList = book.Get< List <ParseObject>>( "authors" ); 
var  authorList = book. get ( "authors" ) 
# No REST API example 
// No C++ example 

Finally, suppose you have an Author and you want to find all the Book objects in which she appears. This is also a pretty straightforward query with an associated constraint:

// set up our query for the Book object  ParseQuery bookQuery = ParseQuery.getQuery( "Book" ); // configure any constraints on your query...  booKQuery.whereEqualTo( "authors" , author); // tell the query to fetch all of the Author objects along with the Book  bookQuery.include( "authors" ); // execute the query  bookQuery.findInBackground(newFindCallback<ParseObject>() { public void done (List<ParseObject> bookList, ParseException e) { } }); 
// suppose we have an Author object  PFObject *author = ... // set up our query for the Book object  PFQuery *bookQuery = [PFQuery queryWithClassName: @"Book" ]; // configure any constraints on your query...  [bookQuery whereKey: @"authors"  equalTo:author]; // tell the query to fetch all of the Author objects along with the Book  [bookQuery includeKey: @"authors" ]; // execute the query  [bookQuery findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { // objects is all of the Book objects, and their associated Author objects, too  }]; 
// suppose we have an Author object let  author = ... // set up our query for the Book object let  bookQuery = PFQuery(className: "Book" ) // configure any constraints on your query...  bookQuery.whereKey( "authors" , equalTo: author) // tell the query to fetch all of the Author objects along with the Book  bookQuery.includeKey( "authors" ) // execute the query  bookQuery.findObjectsInBackgroundWithBlock{ (objects: [AnyObject]?, error: NSError?) -> Void in // objects is all of the Book objects, and their associated Author objects, too  } 
// set up our query for the Book object $bookQuery  = new  ParseQuery( "Book" ); // configure any constraints on your query... $bookQuery ->equalTo( "authors" , $author ); // tell the query to fetch all of the Author objects along with the Book $bookQuery ->includeKey( "authors" ); // execute the query $books  = $bookQuery ->find(); 
// set up our query for the Book object var  bookQuery = ParseObject.GetQuery( "Book" ); // configure any constraints on your query...  bookQuery = bookQuery.WhereEqualTo( "authors" , author); // tell the query to fetch all of the Author objects along with the Book  bookQuery = bookQuery.Include( "authors" ); // execute the query  IEnumerable<ParseObject> books = await  bookQuery.FindAsync(); 
// set up our query for the Book object var  bookQuery = new  Parse.Query( "Book" ); // configure any constraints on your query...  bookQuery.equalTo( "authors" , author); // tell the query to fetch all of the Author objects along with the Book  bookQuery. include ( "authors" ); // execute the query  bookQuery.find({ success: function (books) { ... } }); 
# No REST API example 
// No C++ example 

One-to-One

In Parse, a one-to-one relationship is great for situations where you need to split one object into two objects. These situations should be rare, but two examples include:

  • Limiting visibility of some user data. In this scenario, you would split the object in two, where one portion of the object contains data that is visible to other users, while the related object contains data that is private to the original user (and protected via ACLs).
  • Splitting up an object for size. In this scenario, your original object is greater than the 128K maximum size permitted for an object, so you decide to create a secondary object to house extra data. It is usually better to design your data model to avoid objects this large, rather than splitting them up. If you can't avoid doing so, you can also consider storing large data in a Parse File.

Thank you for reading this far. We apologize for the complexity. Modeling relationships in data is a hard subject, in general. But look on the bright side: it's still easier than relationships with people.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Security

As your app development progresses, you will want to use Parse's security features in order to safeguard data. This document explains the ways in which you can secure your apps.

If your app is compromised, it's not only you as the developer who suffers, but potentially the users of your app as well. Continue reading for our suggestions for sensible defaults and precautions to take before releasing your app into the wild.

Client vs. Server

When an app first connects to Parse, it identifies itself with an Application ID and a Client key (or REST Key, or .NET Key, or JavaScript Key, depending on which platform you're using). These are not secret and by themselves they do not secure an app. These keys are shipped as a part of your app, and anyone can decompile your app or proxy network traffic from their device to find your client key. This exploit is even easier with JavaScript — one can simply "view source" in the browser and immediately find your client key.

This is why Parse has many other security features to help you secure your data. The client key is given out to your users, so anything that can be done with just the client key is doable by the general public, even malicious hackers.

The master key, on the other hand, is definitely a security mechanism. Using the master key allows you to bypass all of your app's security mechanisms, such as class-level permissions and ACLs . Having the master key is like having root access to your app's servers, and you should guard your master key with the same zeal with which you would guard your production machines' root password.

The overall philosophy is to limit the power of your clients (using client keys), and to perform any sensitive actions requiring the master key in Cloud Code. You'll learn how to best wield this power in the section titled Implementing Business Logic in Cloud Code .

A final note: All connections are made with HTTPS and SSL, and Parse will reject all non-HTTPS connections. As a result, you don't need to worry about man-in-the-middle attacks.

Class-Level Permissions

The second level of security is at the schema and data level. Enforcing security measures at this level will restrict how and when client applications can access and create data on Parse. When you first begin developing your Parse application, all of the defaults are set so that you can be a more productive developer. For example:

  • A client application can create new classes on Parse
  • A client application can add fields to classes
  • A client application can modify or query for objects on Parse

You can configure any of these permissions to apply to everyone, no one, or to specific users or roles in your app. Roles are groups that contain users or other roles, which you can assign to an object to restrict its use. Any permission granted to a role is also granted to any of its children, whether they are users or other roles, enabling you to create an access hierarchy for your apps. Each of the Parse guides includes a detailed description of employing Roles in your apps.

Once you are confident that you have the right classes and relationships between classes in your app, you should begin to lock it down by doing the following:

Almost every class that you create should have these permissions tweaked to some degree. For classes where every object has the same permissions, class-level settings will be most effective. For example, one common use case entails having a class of static data that can be read by anyone but written by no one.

Restricting class creation

As a start, you can configure your application so that clients cannot create new classes on Parse. This is done from the Settings tab on the Data Browser. Scroll down to the App Permissions section and turn off Allow client class creation . Once enabled, classes may only be created from the Data Browser. This will prevent attackers from filling your database with unlimited, arbitrary new classes.

Configuring Class-Level Permissions

Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes. To change these settings, go to the Data Browser, select a class, and click the "Security" button.

You can configure the client's ability to perform each of the following operations for the selected class:

  • Read :

    • Get : With Get permission, users can fetch objects in this table if they know their objectIds.

    • Find : Anyone with Find permission can query all of the objects in the table, even if they don’t know their objectIds. Any table with public Find permission will be completely readable by the public, unless you put an ACL on each object.

  • Write :

    • Update : Anyone with Update permission can modify the fields of any object in the table that doesn't have an ACL. For publicly readable data, such as game levels or assets, you should disable this permission.

    • Create : Like Update, anyone with Create permission can create new objects of a class. As with the Update permission, you'll probably want to turn this off for publicly readable data.

    • Delete : With this permission, people can delete any object in the table that doesn't have an ACL. All they need is its objectId.

  • Add fields : Parse classes have schemas that are inferred when objects are created. While you're developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it's very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.

For each of the above actions, you can grant permission to all users (which is the default), or lock permissions down to a list of roles and users. For example, a class that should be available to all users would be set to read-only by only enabling get and find. A logging class could be set to write-only by only allowing creates. You could enable moderation of user-generated content by providing update and delete access to a particular set of users or roles.

Object-Level Access Control

Once you've locked down your schema and class-level permissions, it's time to think about how data is accessed by your users. Object-level access control enables one user's data to be kept separate from another's, because sometimes different objects in a class need to be accessible by different people. For example, a user’s private personal data should be accessible only to them.

Parse also supports the notion of anonymous users for those apps that want to store and protect user-specific data without requiring explicit login.

When a user logs into an app, they initiate a session with Parse. Through this session they can add and modify their own data but are prevented from modifying other users' data.

Access Control Lists

The easiest way to control who can access which data is through access control lists, commonly known as ACLs. The idea behind an ACL is that each object has a list of users and roles along with what permissions that user or role has. A user needs read permissions (or must belong to a role that has read permissions) in order to retrieve an object's data, and a user needs write permissions (or must belong to a role that has write permissions) in order to update or delete that object.

Once you have a User, you can start using ACLs. Remember: Users can be created through traditional username/password signup, through a third-party login system like Facebook or Twitter, or even by using Parse's automatic anonymous users functionality. To set an ACL on the current user's data to not be publicly readable, all you have to do is:

PFUser  *user = [ PFUser  currentUser]; user. ACL  = [ PFACL ACLWithUser :user ]; 
if let  user = PFUser. currentUser () { user.ACL = PFACL(user: user) } 
ParseUser user = ParseUser.getCurrentUser(); user.setACL( new  ParseACL(user)); 
var  user = Parse.User.current(); user.setACL( new  Parse.ACL(user)); 
var  user = ParseUser.CurrentUser; user.ACL = new  ParseACL(user); 
$user  = ParseUser::getCurrentUser(); $user ->setACL( new  ParseACL( $user )) 
# No command line  example 
// No C++ example 

Most apps should do this. If you store any sensitive user data, such as email addresses or phone numbers, you need to set an ACL like this so that the user's private information isn't visible to other users. If an object doesn't have an ACL, it's readable and writeable by everyone. The only exception is the _User class. We never allow users to write each other's data, but they can read it by default. (If you as the developer need to update other _User objects, remember that your master key can provide the power to do this.)

To make it super easy to create user-private ACLs for every object, we have a way to set a default ACL that will be used for every new object you create:

[PFACL setDefaultACL:[PFACL ACL] withAccessForCurrentUser :YES ]; 
PFACL.setDefaultACL(PFACL(), withAccessForCurrentUser: true ) 
ParseACL.setDefaultACL( new  ParseACL(), true ); 
// not available in the JavaScript SDK 
// not  available in  the . NET  SDK 
ParseACL::setDefaultACL( new  ParseACL(), true ); 
# No command line  example 
// No C++ example 

If you want the user to have some data that is public and some that is private, it's best to have two separate objects. You can add a pointer to the private data from the public one.

PFObject *privateData = [PFObject objectWithClassName:@ "PrivateUserData" ]; privateData.ACL = [PFACL ACLWithUser:[PFUser currentUser]]; [privateData set Object:@ "555-5309" for Key:@ "phoneNumber" ]; [[PFUser currentUser] set Object:privateData for Key:@ "privateData" ]; 
if let  currentUser = PFUser. currentUser () { let  privateData = PFObject(className: "PrivateUserData" ) privateData.ACL = PFACL(user: currentUser) privateData.setObject( "555-5309" , for Key: "phoneNumber" ) currentUser.setObject(privateData, for Key: "privateData" ) } 
ParseObject privateData = new  ParseObject( "PrivateUserData" ); privateData.setACL( new  ParseACL(ParseUser.getCurrentUser())); privateData.put( "phoneNumber" , "555-5309" ); ParseUser.getCurrentUser().put( "privateData" , privateData); 
var  privateData = Parse.Object.extend( "PrivateUserData" ); privateData.setACL( new  Parse.ACL(Parse.User.current())); privateData. set ( "phoneNumber" , "555-5309" ); Parse.User.current(). set ( "privateData" , privateData); 
var  privateData = new  ParseObject( "PrivateUserData" ); privateData.ACL = new  ParseACL(ParseUser.CurrentUser); privateData[ "phoneNumber" ] = "555-5309" ; ParseUser.CurrentUser[ "privateData" ] = privateData; 
$privateData  = ParseObject::create( "PrivateUserData" ); $privateData ->setACL( new  ParseACL(ParseUser::getCurrentUser())); $privateData ->set( "phoneNumber" , "555-5309" ); ParseUser::getCurrentUser()->set( "privateData" , $privateData ); 
# No command line  example 
// No C++ example 

Of course, you can set different read and write permissions on an object. For example, this is how you would create an ACL for a public post by a user, where anyone can read it:

PFACL *acl = [PFACL ACL]; [acl set PublicReadAccess: true ]; [acl set WriteAccess: true for User:[PFUser currentUser]]; 
let  acl = PFACL() acl.setPublicReadAccess( true ) if let  currentUser = PFUser. currentUser () { acl.setWriteAccess( true , for User: currentUser) } 
ParseACL acl = new  ParseACL(); acl.setPublicReadAccess( true ); acl.setWriteAccess(ParseUser.getCurrentUser(), true ); 
var  acl = new  Parse.ACL(); acl.setPublicReadAccess( true ); acl.setWriteAccess(Parse.User.current().id, true ); 
var  acl = new  ParseACL(); acl.PublicReadAccess = true ; acl.SetRoleWriteAccess(ParseUser.CurrentUser.ObjectId, true ); 
$acl  = new  ParseACL(); $acl ->setPublicReadAccess( true ); $acl ->setWriteAccess(ParseUser::getCurrentUser(), true ); 
# No command line  example 
// No C++ example 

Sometimes it's inconvenient to manage permissions on a per-user basis, and you want to have groups of users who get treated the same (like a set of admins with special powers). Roles are are a special kind of object that let you create a group of users that can all be assigned to the ACL. The best thing about roles is that you can add and remove users from a role without having to update every single object that is restricted to that role. To create an object that is writeable only by admins:

// Assuming you've already created a role called "admins"...  PFACL *acl = [PFACL ACL]; [acl setPublicReadAccess: true ]; [acl setWriteAccess: true  forRoleWithName:@ "admins" ]; 
let  acl = PFACL() acl.setPublicReadAccess( true ) acl.setWriteAccess( true , for RoleWithName: "admins" ) 
// Assuming you've already created a role called "admins"...  ParseACL acl = new  ParseACL(); acl.setPublicReadAccess( true ); acl.setRoleWriteAccess( "admins" , true ); 
var  acl = new  Parse.ACL(); acl.setPublicReadAccess( true ); acl.setRoleWriteAccess( "admins" , true ); 
var  acl = new  ParseACL(); acl.PublicReadAccess = true ; acl.SetRoleWriteAccess( "admins" , true ); 
$acl  = new  ParseACL(); $acl ->setPublicReadAccess( true ); $acl ->setRoleWriteAccessWithName( "admins" , true ); 
# No command line  example 
// No C++ example 

Of course, this snippet assumes you've already created a role named "admins". This is often reasonable when you have a small set of special roles set up while developing your app. Roles can also be created and updated on the fly — for example, adding new friends to a "friendOf _ " role after each connection is made.

All this is just the beginning. Applications can enforce all sorts of complex access patterns through ACLs and class-level permissions. For example:

  • For private data, read and write access can be restricted to the owner.
  • For a post on a message board, the author and members of the "Moderators" role can have "write" access, and the general public can have "read" access.
  • For logging data that will only be accessed by the developer through the REST API using the master key, the ACL can deny all permissions.
  • Data created by a privileged group of users or the developer, like a global message of the day, can have public read access but restrict write access to an "Administrators" role.
  • A message sent from one user to another can give "read" and "write" access just to those users.

For the curious, here's the format for an ACL that restricts read and write permissions to the owner (whose objectId is identified by "aSaMpLeUsErId" ) and enables other users to read the object:

{ " * ": { " read ": true } , " aSaMpLeUsErId ": { " read " : true , " write ": true } } 

And here's another example of the format of an ACL that uses a Role:

{ " role:RoleName ": { " read ": true } , " aSaMpLeUsErId ": { " read ": true , " write ": true } } 

Pointer Permissions

Pointer permissions are a special type of class-level permission that create a virtual ACL on every object in a class, based on users stored in pointer fields on those objects. For example, given a class with an owner field, setting a read pointer permission on owner will make each object in the class only readable by the user in that object's owner field. For a class with a sender and a reciever field, a read pointer permission on the receiver field and a read and write pointer permission on the sender field will make each object in the class readable by the user in the sender and receiver field, and writable only by the user in the sender field.

Given that objects often already have pointers to the user(s) that should have permissions on the object, pointer permissions provide a simple and fast solution for securing your app using data which is already there, that doesn't require writing any client code or cloud code.

Pointer permissions are like virtual ACLs. They don't appear in the ACL column, buf if you are familiar with how ACLs work, you can think of them like ACLs. In the above example with the sender and receiver , each object will act as if it has an ACL of:

{ " <SENDER_USER_ID> ": { " read ": true , " write ": true } , " <RECEIVER_USER_ID> ": { " read ": true } } 

Note that this ACL is not actually created on each object. Any existing ACLs will not be modified when you add or remove pointer permissions, and any user attempting to interact with an object can only interact with the object if both the virtual ACL created by the pointer permissions, and the real ACL already on the object allow the interaction. For this reason, it can sometimes be confusing to combine pointer permissions and ACLs, so we recommend using pointer permissions for classes that don't have many ACLs set. Fortunately, it's easy to remove pointer permissions if you later decide to use Cloud Code or ACLs to secure your app.

CLP and ACL interaction

Class-Level Permissions (CLPs) and Access Control Lists (ACLs) are both powerful tools for securing your app, but they don't always interact exactly how you might expect. They actually represent two separate layers of security that each request has to pass through to return the correct information or make the intended change. These layers, one at the class level, and one at the object level, are shown below. A request must pass through BOTH layers of checks in order to be authorized. Note that despite acting similarly to ACLs, Pointer Permissions are a type of class level permission, so a request must pass the pointer permission check in order to pass the CLP check.

As you can see, whether a user is authorized to make a request can become complicated when you use both CLPs and ACLs. Let's look at an example to get a better sense of how CLPs and ACLs can interact. Say we have a Photo class, with an object, photoObject . There are 2 users in our app, user1 and user2 . Now lets say we set a Get CLP on the Photo class, disabling public Get, but allowing user1 to perform Get. Now let's also set an ACL on photoObject to allow Read - which includes GET - for only user2 .

You may expect this will allow both user1 and user2 to Get photoObject , but because the CLP layer of authentication and the ACL layer are both in effect at all times, it actually makes it so neither user1 nor user2 can Get photoObject . If user1 tries to Get photoObject , it will get through the CLP layer of authentication, but then will be rejected because it does not pass the ACL layer. In the same way, if user2 tries to Get photoObject , it will also be rejected at the CLP layer of authentication.

Now lets look at example that uses Pointer Permissions. Say we have a Post class, with an object, myPost . There are 2 users in our app, poster , and viewer . Lets say we add a pointer permission that gives anyone in the Creator field of the Post class read and write access to the object, and for the myPost object, poster is the user in that field. There is also an ACL on the object that gives read access to viewer . You may expect that this will allow poster to read and edit myPost , and viewer to read it, but viewer will be rejected by the Pointer Permission, and poster will be rejected by the ACL, so again, neither user will be able to access the object.

Because of the complex interaction between CLPs, Pointer Permissions, and ACLs, we recommend being careful when using them together. Often it can be useful to use CLPs only to disable all permissions for a certain request type, and then using Pointer Permissions or ACLs for other request types. For example, you may want to disable Delete for a Photo class, but then put a Pointer Permission on Photo so the user who created it can edit it, just not delete it. Because of the especially complex way that Pointer Permissions and ACLs interact, we usually recommend only using one of those two types of security mechanisms.

Security Edge Cases

There are some special classes in Parse that don't follow all of the same security rules as every other class. Not all classes follow Class-Level Permissions (CLPs) or Access Control Lists (ACLs) exactly how they are defined, and here those exceptions are documented. Here "normal behavior" refers to CLPs and ACLs working normally, while any other special behaviors are described in the footnotes.

_User _Installation
Get normal behaviour [1, 2, 3] ignores CLP, but not ACL
Find normal behavior [3] master key only [6]
Create normal behavior [4] ignores CLP
Update normal behavior [5] ignores CLP, but not ACL [7]
Delete normal behavior [5] master key only [7]
Add Field normal behavior normal behavior
  1. Logging in, or /1/login in the REST API, does not respect the Get CLP on the user class. Login works just based on username and password, and cannot be disabled using CLPs.

  2. Retrieving the current user, or becoming a User based on a session token, which are both /1/users/me in the REST API, do not respect the Get CLP on the user class.

  3. Read ACLs do not apply to the logged in user. For example, if all users have ACLs with Read disabled, then doing a find query over users will still return the logged in user. However, if the Find CLP is disabled, then trying to perform a find on users will still return an error.

  4. Create CLPs also apply to signing up. So disabling Create CLPs on the user class also disables people from signing up without the master key.

  5. Users can only Update and Delete themselves. Public CLPs for Update and Delete may still apply. For example, if you disable public Update for the user class, then users cannot edit themselves. But no matter what the write ACL on a user is, that user can still Update or Delete itself, and no other user can Update or Delete that user. As always, however, using the master key allows users to update other users, independent of CLPs or ACLs.

  6. Get requests on installations follow ACLs normally. Find requests without master key is not allowed unless you supply the installationId as a constraint.

  7. Update requests on installations do adhere to the ACL defined on the installation, but Delete requests are master-key-only. For more information about how installations work, check out the installations section of the REST guide .

Data Integrity in Cloud Code

For most apps, care around keys, class-level permissions, and object-level ACLs are all you need to keep your app and your users' data safe. Sometimes, though, you'll run into an edge case where they aren't quite enough. For everything else, there's Cloud Code .

Cloud Code allows you to upload JavaScript to Parse's servers, where we will run it for you. Unlike client code running on users' devices that may have been tampered with, Cloud Code is guaranteed to be the code that you've written, so it can be trusted with more responsibility.

One particularly common use case for Cloud Code is preventing invalid data from being stored. For this sort of situation, it's particularly important that a malicious client not be able to bypass the validation logic.

To create validation functions, Cloud Code allows you to implement a beforeSave trigger for your class. These triggers are run whenever an object is saved, and allow you to modify the object or completely reject a save. For example, this is how you create a Cloud Code beforeSave trigger to make sure every user has an email address set:

Parse.Cloud.beforeSave(Parse.User, function (request, response) { var  user = request.object; if  (!user.get( "email" )) { response.error( "Every user must have an email address." ); } else  { response.success(); } }); 

Our Cloud Code guide provides instructions on how to upload this trigger to our servers.

Validations can lock down your app so that only certain values are acceptable. You can also use afterSave validations to normalize your data (e.g. formatting all phone numbers or currency identically). You get to retain most of the productivity benefits of accessing Parse data directly from your client applications, but you can also enforce certain invariants for your data on the fly.

Common scenarios that warrant validation include:

  • Making sure phone numbers have the right format
  • Sanitizing data so that its format is normalized
  • Making sure that an email address looks like a real email address
  • Requiring that every user specifies an age within a particular range
  • Not letting users directly change a calculated field
  • Not letting users delete specific objects unless certain conditions are met

Implementing Business Logic in Cloud Code

While validation often makes sense in Cloud Code, there are likely certain actions that are particularly sensitive, and should be as carefully guarded as possible. In these cases, you can remove permissions or the logic from clients entirely and instead funnel all such operations to Cloud Code functions.

When a Cloud Code function is called, it can invoke the useMasterKey function to gain the ability to modify user data. With the master key, your Cloud Code function can override any ACLs and write data. This means that it'll bypass all the security mechanisms you've put in place in the previous sections.

Say you want to allow a user to "like" a Post object without giving them full write permissions on the object. You can do this by having the client call a Cloud Code function instead of modifying the Post itself:

The master key should be used carefully. When invoked, the master key is in effect for the duration of the Cloud Code function in which it is called:

Parse.Cloud.define( "like" , function (request, response) { Parse.Cloud.useMasterKey(); // Everything after this point will bypass ACLs and other security // even if I do things besides just updating a Post object.  }); 

A more prudent way to use the master key would be to pass it as a parameter on a per-function basis. For example, instead of the above, set useMasterKey to true in each individual API function:

Parse.Cloud.define( "like" , function (request, response) { var  post = new  Parse.Object( "Post" ); post.id = request.params.postId; post.increment( "likes" ); post.save( null , { useMasterKey: true  }).then( function () { // If I choose to do something else here, it won't be using // the master key and I'll be subject to ordinary security measures.  response.success(); }, function (error) { response.error(error); }); }); 

One very common use case for Cloud Code is sending push notifications to particular users. In general, clients can't be trusted to send push notifications directly, because they could modify the alert text, or push to people they shouldn't be able to. Your app's settings will allow you to set whether "client push" is enabled or not; we recommend that you make sure it's disabled. Instead, you should write Cloud Code functions that validate the data to be pushed and sent before sending a push.

Parse Security Summary

Parse provides a number of ways for you to secure data in your app. As you build your app and evaluate the kinds of data you will be storing, you can make the decision about which implementation to choose.

It is worth repeating that that the Parse User object is readable by all other users by default. You will want to set the ACL on your User object accordingly if you wish to prevent data contained in the User object (for example, the user's email address) from being visible by other users.

Most classes in your app will fall into one of a couple of easy-to-secure categories. For fully public data, you can use class-level permissions to lock down the table to put publicly readable and writeable by no one. For fully private data, you can use ACLs to make sure that only the user who owns the data can read it. But occasionally, you'll run into situations where you don't want data that’s fully public or fully private. For example, you may have a social app, where you have data for a user that should be readable only to friends whom they’ve approved. For this you'll need to a combination of the techniques discussed in this guide to enable exactly the sharing rules you desire.

We hope that you'll use these tools to do everything you can to keep your app's data and your users' data secure. Together, we can make the web a safer place.

Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Performance

As your app scales, you will want to ensure that it performs well under increased load and usage. There are parts of optimizing performance that Parse takes care of but there are some things you can do. This document provides guidelines on how you can optimize your app's performance. While you can use Parse for quick prototyping and not worry about performance, you will want to keep our performance guidelines in mind when you're initially designing your app. We strongly advise that you make sure you've followed all suggestions before releasing your app.

Parse provides services out of the box to help your app scale auto-magically. On top of our MongoDB datastore, we have built an API layer that seamlessly integrates with our client-side SDKs. Our cloud infrastructure uses online learning algorithms to automatically rewrite inefficient queries and generate database indexes based on your app’s realtime query stream.

In addition to what Parse provides, you can improve your app's performance by looking at the following:

  • Writing efficient queries.
  • Writing restrictive queries.
  • Using client-side caching.
  • Using Cloud Code.
  • Avoiding count queries.
  • Using efficient search techniques.

Keep in mind that not all suggestions may apply to your app. Let's look into each one of these in more detail.

Write Efficient Queries

Parse objects are stored in a database. A Parse query retrieves objects that you are interested in based on conditions you apply to the query. To avoid looking through all the data present in a particular Parse class for every query, the database can use an index. An index is a sorted list of items matching a given criteria. Indexes help because they allow the database to do an efficient search and return matching results without looking at all of the data. Indexes are typically smaller in size and available in memory, resulting in faster lookups.

Smart Indexing

The key to writing efficient queries is understanding our indexing strategy. If your data is not indexed, every query will have to go through the the entire data for a class to return a query result. On the other hand, if your data is indexed appropriately, the number of documents scanned to return a correct query result should be low.

One of the advantages to using Parse if that you don't have to worry about managing your own database and maintaining indexes. We've built an abstraction to manage all that complexity. However, you do have to organize your data model and use performant queries to take advantage of this. To better understand how to go about doing this, you need to understand how our systems are operating behind the abstraction. The key strategy you will want to understand here is our use of smart indexing.

Smart indexing means that we algorithmically generate indexes for the apps that we host. The sheer number of apps hosted on Parse means that we cannot manually generate indexes for each app. This would not scale well as developers can change their schemas or query patterns at any time. This is why we rely on smart indexes.

We perform two types of index creation logic. The first generates simple (single field) indexes for each API request, and the second does offline processing to pick good compound indexes based on real API traffic patterns. In each case the goal is to pick indexes that result in the smallest search space for the query, that is, there will be less data to scan to find results.

The simple indexing strategy looks at every API request and attempts to pick good indexes based on the following:

  • The query constraint's likelihood of providing the smallest search space.
  • The number of possible values a field may have based on its data type. We consider data types with a larger number of possible values to have a higher entropy.

The order of a query constraint's usefulness is:

  • Equal to
  • Contained In
  • Less than, Less than or Equal to, Greater than, Greater than or Equal to
  • Prefix string matches
  • Not equal to
  • Not contained in
  • Everything else

Take a look at the following query to retrieve GameScore objects:

var  GameScore = Parse.Object.extend( "GameScore" ); var  query = new  Parse.Query(GameScore); query.equalTo( "score" , 50 ); query.containedIn( "playerName" , [ "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon" ]); 
PFQuery *query = [PFQuery queryWithClassName:@ "GameScore" ]; [query where Key:@ "score"  equalTo:@ 50 ]; [query where Key:@ "playerName"  containedIn:@[@ "Jonathan Walsh" , @ "Dario Wunsch" , @ "Shawn Simon" ]]; 
let  query = PFQuery.queryWithClassName( "GameScore" ) query.whereKey( "score" , equalTo: 50 ) query.whereKey( "playerName" , containedIn: [ "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon" ]) 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "GameScore" ); query.whereEqualTo( "score" , 50 ); query.whereContainedIn( "playerName" , Arrays.asList( "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon" )); 
var  names = new [] { "Jonathan Walsh" , "Dario Wunsch" , "Shawn Simon"  }; var  query = new  ParseObject.GetQuery( "GameScore" ) .WhereEqualTo( "score" , 50 ) .WhereContainedIn( "playerName" , names); 
# No REST API example 
// No C++ example 

Creating an index query based on the score field would yield a smaller search space in general than creating one on the playerName field.

When examining data types, booleans have a very low entropy and and do not make good indexes. Take the following query constraint:

query.equalTo( "cheatMode" , false ); 
[query where Key:@ "cheatMode"  equalTo:@NO]; 
query.whereKey( "cheatMode" , equalTo: false ) 
query.whereEqualTo( "cheatMode" , false ); 
query.WhereEqualTo( "cheatMode" , false ); 
# No REST API example 
// No C++ example 

The two possible values for "cheatMode" are true and false . If an index was added on this field it would be of little use because it's likely that 50% of the records will have to be looked at to return query results.

We also throw out relations and join tables, since no values are stored for these keys. We heavily promote GeoPoints since MongoDB won’t run a geo query without a geo index. Other data types are ranked by their expected entropy of the value space for the key:

  • Array
  • Pointer
  • Date
  • String
  • Number
  • Other

We score each query according to the above metrics, and make sure we create a unique index on the three top-scoring fields for each query. For a compound query that consists of an OR of subqueries, we compute the top three indexes for each subquery.

Even the best indexing strategy can be defeated by suboptimal queries. You will need to design queries that work hand in hand with smart indexing to deliver performant apps.

Efficient Query Design

Writing efficient queries means taking full advantage of indexes. Let's take a look at some query constraints that negate the use of indexes:

  • Not Equal To
  • Not Contained In

Additionally, the following queries under certain scenarios may result in slow query responses if they can't take advantage of indexes:

  • Regular Expressions
  • Ordered By

Not Equal To

For example, let's say you're tracking high scores for a game in a GameScore class. Now say you want to retrieve the scores for all players except a certain one. You could create this query:

var  GameScore = Parse.Object.extend( "GameScore" ); var  query = new  Parse.Query(GameScore); query.notEqualTo( "playerName" , "Michael Yabuti" ); query.find().then( function (results) { // Retrieved scores successfully  }); 
PFQuery *query = [PFQuery queryWithClassName: @"GameScore" ]; [query whereKey: @"playerName"  notEqualTo: @"Michael Yabuti" ]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { if  (!error) { // Retrieved scores successfully  } }]; 
let  query = PFQuery.queryWithClassName( "GameScore" ) query.whereKey( "playerName" , notEqualTo: "Michael Yabuti" ) query.findObjectsInBackgroundWithBlock { (objects, error) in if  !error { // Retrieved scores successfully  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "GameScore" ); query.whereNotEqualTo( "playerName" , "Michael Yabuti" ); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  ( e == null ) { // Retrieved scores successfully  } } }); 
var  results = await  ParseObject.GetQuery( "GameScore" ) .WhereNotEqualTo( "playerName" , "Michael Yabuti" ) .FindAsync(); 
# No REST API example 
// No C++ example 

This query can't take advantage of indexes. The database has to look at all the objects in the "GameScore" class to satisfy the constraint and retrieve the results. As the number of entries in the class grows, the query takes longer to run.

Luckily, most of the time a “Not Equal To” query condition can be rewritten as a “Contained In” condition. Instead of querying for the absence of values, you ask for values which match the rest of the column values. Doing this allows the database to use an index and your queries will be faster.

For example if the User class has a column called state which has values “SignedUp”, “Verified”, and “Invited”, the slow way to find all users who have used the app at least once would be to run the query:

var  query = new  Parse.Query(Parse.User); query.notEqualTo( "state" , "Invited" ); 
PFQuery *query = [PFUser query]; [query where Key:@ "state"  notEqualTo:@ "Invited" ]; 
var  query = PFUser.query() query.whereKey( "state" , notEqualTo: "Invited" ) 
ParseQuery<ParseUser> query = ParseQuery.getQuery(ParseUser. class ); query.whereNotEqualTo( "state" , "Invited" ); 
var  query = ParseUser.Query .WhereNotEqualTo( "state" , "Invited" ); 
# No REST API example 
// No C++ example 

It would be faster to use the “Contained In” condition when setting up the query:

query.containedIn( "state" , [ "SignedUp" , "Verified" ]); 
[query where Key:@ "state"  containedIn:@[@ "SignedUp" , @ "Verified" ]]; 
query.whereKey( "state" , containedIn: [ "SignedUp" , "Verified" ]) 
query.whereContainedIn( "state" , Arrays.asList( "SignedUp" , "Verified" )); 
query.WhereContainedIn( "state" , new [] { "SignedUp" , "Verified"  }); 
# No REST API example 
// No C++ example 

Sometimes, you may have to completely rewrite your query. Going back to the "GameScore" example, let's say we were running that query to display players who had scored higher than the given player. We could do this differently, by first getting the given player's high score and then using the following query:

var  GameScore = Parse.Object.extend( "GameScore" ); var  query = new  Parse.Query(GameScore); // Previously retrieved highScore for Michael Yabuti  query.greaterThan( "score" , highScore); query.find().then( function (results) { // Retrieved scores successfully  }); 
PFQuery *query = [PFQuery queryWithClassName: @"GameScore" ]; // Previously retrieved highScore for Michael Yabuti  [query whereKey: @"score"  greaterThan:highScore]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { if  (!error) { // Retrieved scores successfully  } }]; 
let  query = PFQuery.queryWithClassName( "GameScore" ) // Previously retrieved highScore for Michael Yabuti  query.whereKey( "score" , greaterThan: highScore) query.findObjectsInBackgroundWithBlock { (objects, error) in if  !error { // Retrieved scores successfully  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "GameScore" ); // Previously retrieved highScore for Michael Yabuti  query.whereGreaterThan( "score" , highScore); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  (e == null ) { // Retrieved scores successfully  } } }); 
// Previously retrieved highScore for Michael Yabuti var  results = await  ParseObject.GetQuery( "GameScore" ) .WhereGreaterThan( "score" , highScore) .FindAsync(); 
# No REST API example 
// No C++ example 

The new query you use depends on your use case. This may sometimes mean a redesign of your data model.

Not Contained In

Similar to “Not Equal To”, the “Not Contained In” query constraint can't use an index. You should try and use the complementary “Contained In” constraint. Building on the User example, if the state column had one more value, “Blocked”, to represent blocked users, a slow query to find active users would be:

var  query = new  Parse.Query(Parse.User); query.notContainedIn( "state" , [ "Invited" , "Blocked" ]; 
PFQuery *query = [PFUser query]; [query where Key:@ "state"  notContainedIn:@[@ "Invited" , @ "Blocked" ]]; 
var  query = PFUser.query() query.whereKey( "state" , notContainedIn: [ "Invited" , "Blocked" ]) 
ParseQuery<ParseUser> query = ParseQuery.getQuery(ParseUser. class ); query.whereNotContainedIn( "state" , Arrays.asList( "Invited" , "Blocked" )); 
var  query = ParseUser.Query .WhereNotContainedIn( "state" , new [] { "Invited" , "Blocked"  }); 
# No REST API example 
// No C++ example 

Using a complimentary “Contained In” query constraint will always be faster:

query.containedIn( "state" , [ "SignedUp" , "Verified" ]); 
[query where Key:@ "state"  containedIn:@[@ "SignedUp" , @ "Verified" ]]; 
query.whereKey( "state" , containedIn: [ "SignedUp" , "Verified" ]) 
query.whereContainedIn( "state" , Arrays.asList( "SignedUp" , "Verified" )); 
query.WhereContainedIn( "state" , new [] { "SignedUp" , "Verified" }); 
# No REST API example 
// No C++ example 

This means rewriting your queries accordingly. Your query rewrites will depend on your schema set up. It may mean redoing that schema.

Regular Expressions

Most regular expression queries in Parse are heavily throttled due to performance considerations. MongoDB is not efficient for doing partial string matching except for the special case where you only want a prefix match. Queries that have regular expression constraints are therefore very expensive, especially for classes with over 100,000 records. Parse restricts how many such operations can be run on a particular app at any given time.

You should avoid using regular expression constraints that don't use indexes. For example, the following query looks for data with a given string in the "playerName" field. The string search is case insensitive and therefore cannot be indexed:

query.matches( "playerName" , "Michael" , “i”); 
[query where Key:@ "playerName"  matchesRegex:@ "Michael"  modifiers:@ "i" ]; 
query.whereKey( "playerName" , matchesRegex: "Michael" , modifiers: "i" ) 
query.whereMatches( "playerName" , "Michael" , "i" ); 
query.WhereMatches( "playerName" , "Michael" , "i" ) 
# No REST API example 
// No C++ example 

The following query, while case sensitive, looks for any occurrence of the string in the field and cannot be indexed:

query. contains ( "playerName" , "Michael" ); 
[query where Key:@ "playerName"  containsString:@ "Michael" ]; 
query.whereKey( "playerName" , containsString: "Michael" ) 
query.whereContains( "playerName" , "Michael" ); 
query.WhereContains( "playerName" , "Michael" ) 
# No REST API example 
// No C++ example 

These queries are both slow. In fact, the matches and contains query constraints are not covered in our querying guides on purpose and we do not recommend using them. Depending on your use case, you should switch to using the following constraint that uses an index, such as:

query. startsWith ( "playerName" , "Michael" ); 
[query where Key:@ "playerName"  hasPrefix:@ "Michael" ]; 
query.whereKey( "playerName" , hasPrefix: "Michael" ) 
query.whereStartsWith( "playerName" , "Michael" ); 
query.WhereStartsWith( "playerName" , "Michael" ) 
# No REST API example 
// No C++ example 

This looks for data that starts with the given string. This query will use the backend index, so it will be faster even for large datasets.

As a best practice, when you use regular expression constraints, you'll want to ensure that other constraints in the query reduce the result set to the order of hundreds of objects to make the query efficient. If you must use the matches or contains constraints for legacy reasons, then use case sensitive, anchored queries where possible, for example:

query.matches( "playerName" , "^Michael" ); 
[query where Key:@ "playerName"  matchesRegex:@ "^Michael" ]; 
query.whereKey( "playerName" , matchesRegex: "^Michael" ) 
query.whereMatches( "playerName" , "^Michael" ); 
query.WhereMatches( "playerName" , "^Michael" ) 
# No REST API example 
// No C++ example 

Most of the use cases around using regular expressions involve implementing search. A more performant way of implementing search is detailed later.

Write Restrictive Queries

Writing restrictive queries allows you to return only the data that the client needs. This is critical in a mobile environment were data usage can be limited and network connectivity unreliable. You also want your mobile app to appear responsive and this is directly affected by the objects you send back to the client. The Querying Guide shows the types of constraints you can add to your existing queries to limit the data returned. When adding constraints, you want to pay attention and design efficient queries.

You can limit the number of query results returned. The limit is 100 by default but anything from 1 to 1000 is a valid limit:

query.limit( 10 ); // limit  to at most 10  results 
query.limit = 10 ; // limit  to at most 10  results 
query.limit = 10  // limit  to at most 10  results 
query.setLimit( 10 ); // limit  to at most 10  results 
query.Limit( 10 ); // limit  to at most 10  results 
# No REST API example 
// No C++ example 

If you're issuing queries on GeoPoints, make sure you specify a reasonable radius:

var  query = new  Parse.Query(PlaceObject); query.withinMiles( "location" , userGeoPoint, 10.0 ); query.find().then( function (placesObjects) { // Get a list of objects within 10 miles of a user's location  }); 
PFQuery *query = [PFQuery queryWithClassName: @"Place" ]; [query whereKey: @"location"  nearGeoPoint:userGeoPoint withinMiles: 10.0 ]; [query findObjectsInBackgroundWithBlock:^( NSArray  *places, NSError  *error) { if  (!error) { // List of objects within 10 miles of a user's location  } }]; 
let  query = PFQuery.queryWithClassName( "Place" ) query.whereKey( "location" , nearGeoPoint: userGeoPoint, withinMiles: 10.0 ) query.findObjectsInBackgroundWithBlock { (places, error) in if  !error { // List of places within 10 miles of a user's location  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "Place" ); query.whereWithinMiles( "location" , userGeoPoint, 10.0 ); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  (e == null ) { // List of places within 10 miles of a user's location  } } }); 
var  results = await  ParseObject.GetQuery( "GameScore" ) .WhereWithinDistance( "location" , userGeoPoint, ParseGeoDistance.FromMiles( 10.0 )) .FindAsync(); 
# No REST API example 
// No C++ example 

You can further limit the fields returned by calling select:

var  GameScore = Parse.Object.extend( "GameScore" ); var  query = new  Parse.Query(GameScore); query.select( "score" , "playerName" ); query.find().then( function (results) { // each of results will only have the selected fields available.  }); 
PFQuery *query = [PFQuery queryWithClassName: @"GameScore" ]; [query selectKeys:@[ @"score" , @"playerName" ]]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { if  (!error) { // each of results will only have the selected fields available.  } }]; 
let  query = PFQuery.queryWithClassName( "GameScore" ) query.selectKeys([ "score" , "playerName" ]) query.findObjectsInBackgroundWithBlock { (objects, error) in if  !error { // each of results will only have the selected fields available.  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "GameScore" ); query.selectKeys(Arrays.asList( "score" , "playerName" )); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  (e == null ) { // each of results will only have the selected fields available.  } } }); 
var  results = await  ParseObject.GetQuery( "GameScore" ) .Select( new [] { "score" , "playerName"  }) .FindAsync(); // each of results will only have the selected fields available. 
# No REST API example 
// No C++ example 

Client-side Caching

For queries run from iOS and Android, you can turn on query caching. See the iOS and Android guides for more details. Caching queries will increase your mobile app's performance especially in cases where you want to display cached data while fetching the latest data from Parse.

Use Cloud Code

Cloud Code allows you to run custom JavaScript logic on Parse instead of on the client.

You can use this to off load processing to the Parse servers thus increasing your app's perceived performance. You can create hooks that run whenever an object is saved or deleted. This is useful if you want to validate or sanitize your data. You can also use Cloud Code to modify related objects or kick off other processes such as sending off a push notification. There are time limits to how long Cloud Code can run, for example an afterSave hook only has 3 seconds to run. You can use Background Jobs if you need to run more time consuming processes such as data migrations.

We saw examples of limiting the data returned by writing restrictive queries. You can also use Cloud Functions to help limit the amount of data returned to your app. In the following example, we use a Cloud Function to get a movie's average rating:

Parse.Cloud.define( "averageStars" , function (request, response) { var  Review = Parse.Object.extend( "Review" ); var  query = new  Parse.Query(Review); query.equalTo( "movie" , request.params.movie); query.find().then( function (results) { var  sum = 0 ; for  ( var  i = 0 ; i < results.length; ++i) { sum += results[i].get( "stars" ); } response.success(sum / results.length); }, function (error) { response.error( "movie lookup failed" ); }); }); 

You could have ran a query on the Review class on the client, returned only the stars field data and computed the result on the client. As the number of reviews for a movie increases you can see that the data being returned to the device using this methodology also increases. Implementing the functionality through a Cloud Function returns the one result if successful.

As you look at optimizing your queries, you'll find that you may have to change the queries - sometimes even after you've shipped your app to the App Store or Google Play. The ability to change your queries without a client update is possible if you use Cloud Functions . Even if you have to redesign your schema, you could make all the changes in your Cloud Functions while keeping the client interface the same to avoid an app update. Take the average stars Cloud Function example from before, calling it from a client SDK would look like this:

Parse.Cloud.run( "averageStars" , { "movie" : "The Matrix"  }).then( function (ratings) { // ratings is 4.5  }); 
[PFCloud callFunctionInBackground: @"averageStars"  withParameters:@{ @"movie" : @"The Matrix" } block:^( NSNumber  *ratings, NSError  *error) { if  (!error) { // ratings is 4.5  } }]; 
PFCloud.callFunctionInBackground( "averageStars" , withParameters: [ "movie" : "The Matrix" ]) { (ratings, error) in if  !error { // ratings is 4.5  } } 
HashMap<String, String> params  = new  HashMap(); params .put( "movie" , "The Matrix" ); ParseCloud.callFunctionInBackground( "averageStars" , params , new  FunctionCallback<Float>() { @ Override public void done (Float aFloat, ParseException e) { if  (e == null ) { // ratings is 4.5  } } }); 
IDictionary< string , object > dictionary = new  Dictionary< string , object > { { "movie" , "The Matrix"  } }; ParseCloud.CallFunctionAsync< float >( "averageStars" , dictionary).ContinueWith(t => { var  result = t.Result; // result is 4.5  }); 
# No REST API example 
// No C++ example 

If later on, you need to modify the underlying data model, your client call can remain the same, as long as you return back a number that represents the ratings result.

Avoid Count Operations

For classes with over 1,000 objects, count operations are limited by timeouts. They may routinely yield timeout errors or return results that are only approximately correct. Thus, it is preferable to architect your application to avoid this count operation.

Suppose you are displaying movie information in your app and your data model consists of a Movie class and a Review class that contains a pointer to the corresponding movie. You might want to display the review count for each movie on the top-level navigation screen using a query like this:

var  Review = Parse.Object.extend( "Review" ); var  query = new  Parse.Query( "Review" ); // movieId corresponds to a given movie's id  query.equalTo(“movie”, movieId); query.count().then( function (count) { // Request succeeded  }); 
PFQuery *query = [PFQuery queryWithClassName: @"Review" ]; // movieId corresponds to a given movie's id  [query whereKey: @"movie"  equalTo:movieId]; [query countObjectsInBackgroundWithBlock:^( int  number, NSError  *error) { if  (!error) { // Request succeeded  } }]; 
let  query = PFQuery.queryWithClassName( "Review" ) // movieId corresponds to a given movie's id  query.whereKey( "movie" , equalTo: movieId) query.countObjectsInBackgroundWithBlock { (number, error) in if  !error { // Request succeeded  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "Review" ); // movieId corresponds to a given movie's id  query.whereEqualTo( "movie" , movieId); query.countInBackground( new  CountCallback() { @Override public void done ( int  i, ParseException e) { if  ( e == null ) { // Request succeeded  } } }); 
var  count = await  ParseObject.GetQuery( "Review" ) // movieId corresponds to a given movie's id  .WhereEqualTo( "movie" , movieId) .CountAsync(); 
# No REST API example 
// No C++ example 

If you run the count query for each of the UI elements, they will not run efficiently on large data sets. One approach to avoid using the count() operator could be to add a field to the Movie class that represents the review count for that movie. When saving an entry to the Review class you could increment the corresponding movie's review count field. This can be done in an afterSave handler:

Parse.Cloud.afterSave( "Review" , function (request) { // Get the movie id for the Review var  movieId = request.object.get( "movie" ).id; // Query the Movie represented by this review var  Movie = Parse.Object.extend( "Movie" ); var  query = new  Parse.Query(Movie); query.get(movieId).then( function (movie) { // Increment the reviews field on the Movie object  movie.increment( "reviews" ); movie.save(); }, function (error) { throw "Got an error "  + error.code + " : "  + error.message; }); }); 

Your new optimized query would not need to look at the Review class to get the review count:

var  Movie = Parse.Object.extend( "Movie" ); var  query = new  Parse.Query(Movie); query.find().then( function (results) { // Results include the reviews count field  }, function (error) { // Request failed  }); 
PFQuery *query = [PFQuery queryWithClassName: @"Movie" ]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { if  (!error) { // Results include the reviews count field  } }]; 
let  query = PFQuery.queryWithClassName( "Movie" ) query.findObjectsInBackgroundWithBlock { (objects, error) in if  !error { // Results include the reviews count field  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "Movie" ); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  (e == null ) { // Results include the reviews count field  } } }); 
var  results = await  ParseObject.GetQuery( "Movie" ) .FindAsync(); // Results include the reviews count field 
# No REST API example 
// No C++ example 

You could also use a separate Parse Object to keep track of counts for each review. Whenever a review gets added or deleted, you can increment or decrement the counts in an afterSave or afterDelete Cloud Code handler. The approach you choose depends on your use case.

Implement Efficient Searches

As mentioned previously, MongoDB is not efficient for doing partial string matching. However, this is an important use case when implementing search functionality that scales well in production.

Simplistic search algorithms simply scan through all the class data and executes the query on each entry. The key to making searches run efficiently is to minimize the number of data that has to be examined when executing each query by using an index as we've outlined earlier. You’ll need to build your data model in a way that it’s easy for us to build an index for the data you want to be searchable. For example, string matching queries that don’t match an exact prefix of the string won’t be able to use an index leading to timeout errors as the data set grows.

Let's walk through an example of how you could build an efficient search. You can apply the concepts you learn in this example to your use case. Say your app has users making posts, and you want to be able to search those posts for hashtags or particular keywords. You’ll want to pre-process your posts and save the list of hashtags and words into array fields. You can do this processing either in your app before saving the posts, or you can use a Cloud Code beforeSave hook to do this on the fly:

var  _ = require ( "underscore" ); Parse.Cloud.beforeSave( "Post" , function (request, response) { var  post = request.object; var  toLowerCase = function (w) { return  w.toLowerCase(); }; var  words = post.get( "text" ).split( /\b/ ); words = _.map(words, toLowerCase); var  stopWords = [ "the" , "in" , "and" ] words = _.filter(words, function (w) { return  w.match( /^\w+$/ ) && ! _.contains(stopWords, w); }); var  hashtags = post.get( "text" ).match( /#.+?\b/g ); hashtags = _.map(hashtags, toLowerCase); post.set( "words" , words); post.set( "hashtags" , hashtags); response.success(); }); 

This saves your words and hashtags in array fields, which MongoDB will store with a multi-key index. There are some important things to notice about this. First of all it’s converting all words to lower case so that we can look them up with lower case queries, and get case insensitive matching. Secondly, it’s filtering out common words like ‘the’, ‘in’, and ‘and’ which will occur in a lot of posts, to additionally reduce useless scanning of the index when executing the queries.

Once you've got the keywords set up, you can efficiently look them up using “All” constraint on your query:

var  Post = Parse.Object.extend( "Post" ); var  query = new  Parse.Query(Post); query.containsAll( "hashtags" , [“ #parse”, “#ftw”]);  query.find().then( function (results) { // Request succeeded  }, function (error) { // Request failed  }); 
PFQuery *query = [PFQuery queryWithClassName: @"Post" ]; [query whereKey: @"hashtags"  containsAllObjectsInArray:@[ @"#parse" , @"#ftw" ]]; [query findObjectsInBackgroundWithBlock:^( NSArray  *objects, NSError  *error) { if  (!error) { // Request succeeded  } }]; 
let  query = PFQuery.queryWithClassName( "Post" ) query.whereKey( "hashtags" , containsAllObjectsInArray: [ "#parse" , "#ftw" ]) query.findObjectsInBackgroundWithBlock { (objects, error) in if  !error { // Request succeeded  } } 
ParseQuery<ParseObject> query = ParseQuery.getQuery( "Post" ); query.whereContainsAll( "hashtags" , Arrays.asList( "#parse" , "#ftw" )); query.findInBackground( new  FindCallback<ParseObject>() { @Override public void done (List<ParseObject> list, ParseException e) { if  (e == null ) { // Request succeeded  } } }); 
var  results = await  ParseObject.GetQuery( "Post" ) .WhereContainsAll( "hashtags" , new [] { "#parse" , "#ftw"  }) .FindAsync(); 
# No REST API example 
// No C++ example 

Limits and Other Considerations

There are some limits in place to ensure the API can provide the data you need in a performant manner. We may adjust these in the future. Please take a moment to read through the following list:

Objects

  • Parse Objects are limited in size to 128 KB.
  • We recommend against creating more than 64 fields on a single Parse Object to ensure that we can build effective indexes for your queries.
  • We recommend against using field names that are longer than 1,024 characters, otherwise an index for the field will not be created.
  • An app may only create up to 100 Parse Config keys. Use Parse Objects if you need more.
  • An app may only create up to 200 classes.
  • Only the first 100 classes will be displayed in the Data Browser.

Queries

  • Queries return 100 objects by default. Use the limit parameter to change this, up to a value of 1,000.
  • Queries can only return up to 1,000 objects in a single result set. This includes any resolved pointers. You can use skip and limit to page through results.
  • The maximum value accepted by skip is 10,000. If you need to get more objects, we recommend sorting the results and then using a constraint on the sort column to filter out the first 10,000 results. You will then be able to continue paging through results starting from a skip value of 0. For example, you can sort your results by createdAt ASC and then filter out any objects older than the createdAt value of the 10,000th object when starting again from 0.
  • Alternatively, you may use the each() method in the JavaScript SDK to page through all objects that match the query.
  • Skips and limits can only be used on the outer query.
  • You may increase the limit of a inner query to 1,000, but skip cannot be used to get more results past the first 1,000.
  • Constraints that collide with each other will result in only one of the constraint being applied. An example of this would be two equalTo constraints over the same key with two different values, which contradicts itself (perhaps you're looking for 'contains').
  • Regular expression queries are deprecated and an app can make no more than 80 regex queries / minute.
  • Count operations are limited to 160 count queries / minute period for each application. The limit applies to all requests made by all clients of the application. If you need to query counts more frequently, you may want to design your system so the results can be cached in your client application.
  • No geo-queries inside compound OR queries.
  • Using $exists: false is not advised.
  • The each query method in the JavaScript SDK cannot be used in conjunction with queries using geo-point constraints.
  • A maximum of 500,000 objects will be scanned per query. If your constraints do not successfully limit the scope of the search, this can result in queries with incomplete results.
  • A containsAll query constraint can only take up to 9 items in the comparison array.

Files

  • Parse Files are limited to 10 MB each and the limit cannot be increased. There is no limit on how many Parse Files your app can create.
  • Parse Hosting, on the other hand, supports files up to 500 MB, ideal for Unity projects.
  • Parse Files cannot be accessed via HTTPS.

Push Notifications

Cloud Code

  • JavaScript scripts in Cloud Code are limited to 128 KB.
  • Cloud functions must return within 15 seconds. Use webhooks if you need more time.
  • Cloud save/delete hooks must return within 3 seconds. Use webhooks if you need more time.
  • Background jobs will be terminated after 15 minutes.
  • Cloud Code invocations (such as functions, save/delete hooks, background jobs, and custom endpoint handlers) must use a finite amount of memory (currently 384 MB, but subject to change in the future). This memory is garbage collected by the JavaScript engine, so it is generally not an issue. To allow the garbage collector to do its work, make sure that you aren't saving references to large objects, HTTP response buffers, and other objects to global variables in your script.
  • Webhooks must return within 30 seconds.
  • Apps can run one job concurrently by default, but this can be increased by increasing your requests/second in your Account Overview page.
  • Additional background jobs over quota will fail to run. They will not be queued.
  • The params payload that is passed to a Cloud Function is limited to 50 MB.
  • When using console to log information and errors, the message string will be limited to 1 KB.
  • Only the first 100 messages logged by a Cloud function will be persisted in the Cloud Code logs.
  • Parse.Cloud.httpRequest has a 2 second connection timeout. If the connection cannot be established within 2 seconds, you may see a 124 request timed out error.
  • Parse.Cloud.httpRequest has a maximum total timeout of 1 minute. This timeout can be further restricted by the maximum timeout of the particular Cloud Code invocation. For instance, before/after save triggers have a maximum timeout of 3 seconds, so the maximum timeout for an HTTP request made within a before/after save trigger is 3 seconds.
  • There is a limit of 8 concurrent outbound Parse.Cloud.httpRequest requests per Cloud Code request.
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.

Error Codes

The following is a list of all the error codes that can be returned by the Parse API. You may also refer to RFC2616 for a list of http error codes. Make sure to check the error message for more details.

API Issues

Name Code Description
UserInvalidLoginParams 101 Invalid login parameters. Check error message for more details.
ObjectNotFound 101 The specified object or session doesn't exist or could not be found. Can also indicate that you do not have the necessary permissions to read or write this object. Check error message for more details.
InvalidQuery 102 There is a problem with the parameters used to construct this query. This could be an invalid field name or an invalid field type for a specific constraint. Check error message for more details.
InvalidClassName 103 Missing or invalid classname. Classnames are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters.
MissingObjectId 104 An unspecified object id.
InvalidFieldName 105 An invalid field name. Keys are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. Some field names may be reserved. Check error message for more details.
InvalidPointer 106 A malformed pointer was used. You would typically only see this if you have modified a client SDK.
InvalidJSON 107 Badly formed JSON was received upstream. This either indicates you have done something unusual with modifying how things encode to JSON, or the network is failing badly. Can also indicate an invalid utf-8 string or use of multiple form encoded values. Check error message for more details.
CommandUnavailable 108 The feature you tried to access is only available internally for testing purposes.
NotInitialized 109 You must call Parse.initialize before using the Parse library. Check the Quick Start guide for your platform.
ObjectTooLarge 116 The object is too large. ParseObjects have a max size of 128 kilobytes.
ExceededConfigParamsError 116 You have reached the limit of 100 config parameters.
InvalidLimitError 117 An invalid value was set for the limit. Check error message for more details.
InvalidSkipError 118 An invalid value was set for skip. Check error message for more details.
OperationForbidden 119 The operation isn't allowed for clients due to class-level permissions. Check error message for more details.
CacheMiss 120 The result was not found in the cache.
InvalidNestedKey 121 An invalid key was used in a nested JSONObject. Check error message for more details.
InvalidACL 123 An invalid ACL was provided.
InvalidEmailAddress 125 The email address was invalid.
DuplicateValue 137 Unique field was given a value that is already taken.
InvalidRoleName 139 Role's name is invalid.
ReservedValue 139 Field value is reserved.
ExceededCollectionQuota 140 You have reached the quota on the number of classes in your app. Please delete some classes if you need to add a new class.
ScriptFailed 141 Cloud Code script failed. Usually points to a JavaScript error. Check error message for more details.
FunctionNotFound 141 Cloud function not found. Check that the specified Cloud function is present in your Cloud Code script and has been deployed.
JobNotFound 141 Background job not found. Check that the specified job is present in your Cloud Code script and has been deployed.
SuccessErrorNotCalled 141 success/error was not called. A cloud function will return once response.success() or response.error() is called. A background job will similarly finish execution once status.success() or status.error() is called. If a function or job never reaches either of the success/error methods, this error will be returned. This may happen when a function does not handle an error response correctly, preventing code execution from reaching the success() method call.
MultupleSuccessErrorCalls 141 Can't call success/error multiple times. A cloud function will return once response.success() or response.error() is called. A background job will similarly finish execution once status.success() or status.error() is called. If a function or job calls success() and/or error() more than once in a single execution path, this error will be returned.
ValidationFailed 142 Cloud Code validation failed.
WebhookError 143 Webhook error.
InvalidImageData 150 Invalid image data.
UnsavedFileError 151 An unsaved file.
InvalidPushTimeError 152 An invalid push time was specified.
HostingError 158 Hosting error.
InvalidEventName 160 The provided analytics event name is invalid.
ClassNotEmpty 255 Class is not empty and cannot be dropped.
AppNameInvalid 256 App name is invalid.
MissingAPIKeyError 902 The request is missing an API key.
InvalidAPIKeyError 903 The request is using an invalid API key.

Push related errors

Name Code Description
IncorrectType 111 A field was set to an inconsistent type. Check error message for more details.
InvalidChannelName 112 Invalid channel name. A channel name is either an empty string (the broadcast channel) or contains only a-zA-Z0-9_ characters and starts with a letter.
InvalidSubscriptionType 113 Bad subscription type. Check error message for more details.
InvalidDeviceToken 114 The provided device token is invalid.
PushMisconfigured 115 Push is misconfigured in your app. Check error message for more details.
PushWhereAndChannels 115 Can't set channels for a query-targeted push. You can fix this by moving the channels into your push query constraints.
PushWhereAndType 115 Can't set device type for a query-targeted push. You can fix this by incorporating the device type constraints into your push query.
PushMissingData 115 Push is missing a 'data' field.
PushMissingChannels 115 Non-query push is missing a 'channels' field. Fix by passing a 'channels' or 'query' field.
ClientPushDisabled 115 Client-initiated push is not enabled. Check your Parse app's push notification settings.
RestPushDisabled 115 REST-initiated push is not enabled. Check your Parse app's push notification settings.
ClientPushWithURI 115 Client-initiated push cannot use the "uri" option.
PushQueryOrPayloadTooLarge 115 Your push query or data payload is too large. Check error message for more details.
InvalidExpirationError 138 Invalid expiration value.
MissingPushIdError 156 A push id is missing. Deprecated.
MissingDeviceTypeError 157 The device type field is missing. Deprecated.

File related errors

Name Code Description
InvalidFileName 122 An invalid filename was used for File. A valid file name contains only a-zA-Z0-9_. characters and is between 1 and 128 characters.
MissingContentType 126 Missing content type.
MissingContentLength 127 Missing content length.
InvalidContentLength 128 Invalid content length.
FileTooLarge 129 File size exceeds maximum allowed.
FileSaveError 130 Error saving a file.
FileDeleteError 131 File could not be deleted.

Installation related errors

Name Code Description
InvalidInstallationIdError 132 Invalid installation id.
InvalidDeviceTypeError 133 Invalid device type.
InvalidChannelsArrayError 134 Invalid channels array value.
MissingRequiredFieldError 135 Required field is missing.
ChangedImmutableFieldError 136 An immutable field was changed.

Purchase related errors

Name Code Description
ReceiptMissing 143 Product purchase receipt is missing.
InvalidPurchaseReceipt 144 Product purchase receipt is invalid.
PaymentDisabled 145 Payment is disabled on this device.
InvalidProductIdentifier 146 The product identifier is invalid.
ProductNotFoundInAppStore 147 The product is not found in the App Store.
InvalidServerResponse 148 The Apple server response is not valid.
ProductDownloadFilesystemError 149 The product fails to download due to file system error.

User related errors

Name Code Description
UsernameMissing 200 The username is missing or empty.
PasswordMissing 201 The password is missing or empty.
UsernameTaken 202 The username has already been taken.
UserEmailTaken 203 Email has already been used.
UserEmailMissing 204 The email is missing, and must be specified.
UserWithEmailNotFound 205 A user with the specified email was not found.
SessionMissing 206 A user object without a valid session could not be altered.
MustCreateUserThroughSignup 207 A user can only be created through signup.
AccountAlreadyLinked 208 An account being linked is already linked to another user.
InvalidSessionToken 209 The device's session token is no longer valid. The application should ask the user to log in again.

Linked services errors

Name Code Description
LinkedIdMissing 250 A user cannot be linked to an account because that account's id could not be found.
InvalidLinkedSession 251 A user with a linked (e.g. Facebook or Twitter) account has an invalid session. Check error message for more details.
InvalidGeneralAuthData 251 Invalid auth data value used.
BadAnonymousID 251 Anonymous id is not a valid lowercase UUID.
FacebookBadToken 251 The supplied Facebook session token is expired or invalid.
FacebookBadID 251 A user with a linked Facebook account has an invalid session.
FacebookWrongAppID 251 Unacceptable Facebook application id.
TwitterVerificationFailed 251 Twitter credential verification failed.
TwitterWrongID 251 Submitted Twitter id does not match the id associated with the submitted access token.
TwitterWrongScreenName 251 Submitted Twitter handle does not match the handle associated with the submitted access token.
TwitterConnectFailure 251 Twitter credentials could not be verified due to problems accessing the Twitter API.
UnsupportedService 252 A service being linked (e.g. Facebook or Twitter) is unsupported. Check error message for more details.
UsernameSigninDisabled 252 Authentication by username and password is not supported for this application. Check your Parse app's authentication settings.
AnonymousSigninDisabled 252 Anonymous users are not supported for this application. Check your Parse app's authentication settings.
FacebookSigninDisabled 252 Authentication by Facebook is not supported for this application. Check your Parse app's authentication settings.
TwitterSigninDisabled 252 Authentication by Twitter is not supported for this application. Check your Parse app's authentication settings.
InvalidAuthDataError 253 An invalid authData value was passed. Check error message for more details.
LinkingNotSupportedError 999 Linking to an external account not supported yet with signup_or_login. Use update instead.

Client-only errors

Name Code Description
ConnectionFailed 100 The connection to the Parse servers failed.
AggregateError 600 There were multiple errors. Aggregate errors have an "errors" property, which is an array of error objects with more detail about each error that occurred.
FileReadError 601 Unable to read input for a File on the client.
XDomainRequest 602 A real error code is unavailable because we had to use an XDomainRequest object to allow CORS requests in Internet Explorer, which strips the body from HTTP responses that have a non-2XX status code.

Operational issues

Name Code Description
RequestTimeout 124 The request was slow and timed out. Typically this indicates that the request is too expensive to run. You may see this when a Cloud function did not finish before timing out, or when a Parse.Cloud.httpRequest connection times out.
InefficientQueryError 154 An inefficient query was rejected by the server. Refer to the Performance Guide and slow query log.
RequestLimitExceeded 155 This application has exceeded its request limit. Please retry in one minute or raise your request limit at https://parse.com/account . Check error message for more details.
TemporaryRejectionError 159 An application's requests are temporary rejected by the server.

If your requests are rejected with a 155 error code, your application may be exceeding its request limit . You can learn more about how the request limit is calculated in the Pricing FAQ . If waiting one minute or increasing the request limit does not resolve the issue, please file a bug report . If your requests are being rejected with a 159 error code, please file a bug report for further instructions on how to resolve the issue.

Other issues

Name Code Description
OtherCause -1 An unknown error or an error unrelated to Parse occurred.
InternalServerError 1 Internal server error. No information available.
ServiceUnavailable 2 The service is currently unavailable.
ClientDisconnected 4 Connection failure.
Want to contribute to this doc? Edit this section.
Was this section helpful? NO YES Thanks for your feedback, we’re always working to make our docs better.
No matter what you want to build, we’ve got your back.
Get started now