| OpenSocial | OpenSocial and Gadgets Specification Group |
| <opensocial-rpc-protocol-specification-v0_9> | April 15, 2009 |
OpenSocial RPC Protocol Specification v0.9
opensocial-rpc-protocol-specification-v0_9
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119 [RFC2119]. Domain name examples use RFC2606 [RFC2606].
This proposal defines an RPC alternative to the [RESTful Protocol Specification]. The intent is to support the same data and operations as the RESTful API in a form that is more natural for the JSON data format. Any batch of RPCs should be programatically convertible to a sequence or batch of RESTful calls and maintain the same semantics, any exception to this will be called out explicitly.
It shares the following with the RESTful spec:
It differs from the RESTful spec in the following ways:
This document presents all of its examples in the JSON format but the RPC protocol is equally applicable to an XML format. The data structure returned is equivalent in both formats; the only difference is in the encoding of the data. Singular fields are encoded as string key/value pairs in JSON and tags with text content in XML, e.g. "field": "value" and <field>value</field> respectively. Plural fields are encoded as arrays in JSON and repeated tags in XML, e.g. "fields": [ "value1", "value2" ] and<fields>value1</field><fields>value2</field> respectively. Nodes with multiple sub-nodes are represented as objects in JSON and tags with sub-tags in XML, e.g. "field": { "subfield1": "value1", "subfield2": "value2" } and<field><subfield1>value1</subfield1><subfield2> value2</subfield2></field> respectively.
This maps to fetching /people/@me/@self using the RESTful API.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <Auth token>
Content-Type: application/json
{
"method" : "people.get",
"id" : "myself"
"params" : {
"userId" : "@me",
"groupId" : "@self"
}
}
HTTP/1.x 207 Multi-Status
Content-Type: application/json
{
"id" : "myself"
"result" : {
"id" : "example.org:34KJDCSKJN2HHF0DW20394",
"name" : { "unstructured" : "Jane Doe"},
"gender" : "female"
}
}
This maps to fetching /people/@me/@self & /people/@me/@friends using the RESTful API.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <Auth token>
Content-Type: application/json
[
{
"method" : "people.get",
"id" : "myself"
},
{
"method" : "people.get",
"id" : "myfriends"
"params: {
"groupId" : "@friends"
}
}
]
HTTP/1.x 207 Multi-Status
Content-Type: application/json
[
{
"id" : "myself",
"result" : {
"id" : "example.org:34KJDCSKJN2HHF0DW20394",
"name" : { "unstructured" : "Jane Doe"},
"gender" : "female"
}
},
{
"id" : "myfriends"
"error" : {
"code" : 401
}
}
]
This maps to fetching /people/@me/@self using the 1st auth-token and fetching /people/@me/@friends using the 2nd auth-token with the RESTful API.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <1st Auth token>
Content-Type: application/json
[
{
"method" : "people.get",
"id" : "a9fd76"
},
{
"method" : "people.get",
"id" : "e453a",
"params: {
"groupId" : "@friends",
"auth" : "<2nd Auth token>"
}
}
]
HTTP/1.x 207 Multi-Status
Content-Type: application/json
[
{
"result" : {
...
}
},
{
"result" : {
...
}
}
]
This example maps to fetching /people/@me/@friends with the RESTful API.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <auth token>
Content-Type: application/json
{
"method" : "people.get",
"id" : "myfriends"
"params: {
"userId" : "@me",
"groupId" : "@friends"
}
}
HTTP/1.x 207 Multi-Status
Content-Type: application/json
{
"result" : {
"totalResults" : 100,
"startIndex" : 0,
"itemsPerPage" : 10,
"list" : [
{..},
{..},
]
}
}
This example maps to PUTing or POSTing a set of key-values to /appdata/@me/@self/app12345 with the RESTful API. This is a fairly simple example of an update.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <auth token>
Content-Type: application/json
{
"method" : "appdata.update",
"id" : "setMyData"
"params: {
"appId" : "app12345",
"data" : {
"pokes" : 3,
"lastPoke" : "2008-02-13T18:30:02Z"
}
}
}
HTTP/1.x 207 Multi-Status
Content-Type: application/json
{
"result" : {
}
}
This example maps to PUTing an opensocial.Person object /people/@me/@friends with the RESTful API. This example is a use-case not covered by the OpenSocial JS API and is given here to show how additional operations that a container may want to support can be defined within this framework. This effect of this call may be different based on how the container models the social graph. This may be interpreted as send an invite to the specified person to create a bi-drection friend relationship which may be accepted or rejected later. Depending on the semantics of the group the result of the call can vary and the person added may not be immediately visible in a subsequent person.get call on the same group.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <auth token>
Content-Type: application/json
{
"method" : "people.create",
"id" : "createFriend"
"params: {
"userId" : "@me",
"groupId" : "@friends",
"person" : {
"id" : "example.org:FF256337"
}
}
}
HTTP/1.x 207 Multi-Status
Content-Type: application/json
{
"result" : {
}
}
This example maps to POSTing an opensocial.Person object /people/@me/@self with the RESTful API. This example again is not one covered by the OpenSocial JS but may be supported by containers. The example shown demonstrates the use of etags to detect an incompatible update to the profile of the user identified in the auth token. The request is attempting to update the 'books' field and passes the etag the client received when it retrieved the person object prior to update. The etag is added to the update request shown below but an inervening update from another client has made the change incompatible and so the update is rejected.
Notes:
POST /rpc HTTP/1.1
Host: api.example.org
Authorization: <auth token>
Content-Type: application/json
{
"method" : "person.update",
"id" : "setMyData"
"params: {
"userId" : "@me",
"groupId" : "@self",
"person" : {
"etag" : "767ffdef7",
"books" : ["The Prince", "Hotel New Hampshire"]
}
}
}
HTTP/1.x 207 Multi-Status
Content-Type: application/json
{
"id" : "myself",
"error" : {
"code" : 409
"message" : "Conflict detected",
"data" : {
"etag" : "8543de12",
"books" : ["The Right Stuff", "About a boy"]
}
}
}
A multipart form-data request mechanism ("Content-Type: multipart/form-data") enables upload of content. Such a request has the POST body segmented into several sections (fields) with each field identified by a name. The field name "request" is reserved and contains request operation details (same as the contents on the POST body in the vanilla POST operation).
The fields with any other name contain content to be uploaded to the OpenSocial container. To allow batching and multiple file uploads in a single call, multiple fields (identified with unique field names) are allowed. The OpenSocial request can refer to the content being uploaded by setting the URL to "@field:<fieldname>". The container may host the uploaded file and replace the URL field with a fully qualified URL for future access.
For a successful upload, the response code of the request will be associated with the response code of the action request on the resource. However, in case of a failure due to a file size exceeding limits, an error code of 413 ("Request Entity Too Large") MUST be returned. The requested action associated with the file upload MUST NOT be executed.
Example:
<usual headers>
Content-type: multipart/form-data; boundary=------------abcdef012345xyZ
Content-length: <contentLength>
------------abcdef012345xyZ
Content-Disposition: form-data; name="request"
[{
"method":"activities.create",
"params": {
"userId":["@viewer"],
"groupId":"@self",
"appId":"@app",
"activity": {
"title": "hello world!",
"mediaItems": [ {
"mimeType":"image",
"url":"@field:image1"
} ]
}
},
"id":"key"
}
------------abcdef012345xyZ
Content-Disposition: form-data; name="image1"
GIF89....<image data>
------------abcdef012345xyZ
This example illustrates how information for users and a static resources can be invalidated using the cache service. The example shows how to specify a user with a global ID (which include the domain of the container) or a relative ID. The static resource in this example is the gadget spec itself, but it could also be an image or message bundle file.
Example:
POST /api/rpc
HOST opensocial.example.org
Content-Type application/json
{
method : "cache.invalidate",
params : {
invalidationKeys : [ "example.org:12345", "example.org:4567", "3456778", "http://www.myapp.com/gadgetpec.xml"]
}
}
In addition to conforming with the JSON-RPC specification for a container to be an OpenSocial RPC provider they MUST conform to the following conventions.
The value of the "method" field in an OpenSocial RPC request uses a format of "<service-name>.<operation>". <service-name> is a string [A-Za-z0-9_]+ that identifies one of the services listed in [Services] and <operation> is a string [A-Za-z0-9_]+ that identifies an operation supported by that service.
All OpenSocial services MUST expose a "get" [TBD query, fetch are valid alternatives] operation. Operations with the names get, create, update and delete should map to the HTTP equivalent operations in the RESTful API of GET, PUT, POST & DELETE.
The names of the RPC services MUST match the names used in the OpenSocial namespace E.g http://ns.opensocial.org/2008/opensocial/messages.
Calls to the RPC endpoint for batch execution MUST only be supported for HTTP POST requests. A batch of RPC's is an ordered array of requests and the return is an ordered array of responses in the same order. I.e
[ <rpc-request1>, <rpc-request2>, ...] -> [ <rpc-response1>, <rpc-response2>, ...]
If processing of the entire batch fails, as opposed to a single request within the batch, then the returned value is a single JSON-RPC response object with the appropriate error message and code.
Many service operations return a list of OpenSocial resources. Lists are always returned in the "list" field of the result. Lists can either be the full set of resources or a pageable subset. If the returned list represents the entire set of resources the response is simply:
{
"result" : {
"list" : [ ... ]
}
}
If the operation supports random access indexing of the full list it will support the "startIndex" and "count" parameters which control what sublist of the full list is returned. If the response contains a sublist obtained via random access indexing it will have the general form:
{
"result" : {
"totalResults" : <total number of elements in the full list>,
"startIndex" : <0 based offset in the full-list of the returned sublist>,
"itemsPerPage" : <total number of elements in the returned sublist>,
"isFiltered" : <boolean indicating if the result honors filter params in the request>,
"isUpdatedSince" : <boolean indicating if the result honors the updatedSince param in the request>,
"itemsPerPage" : <total number of elements in the returned sublist>,
"list" : [ ... the sublist ...]
}
}
The paging mechanisms described here are based on the OpenSearch standard with the additional requirement that all indexes are 0 based.
Containers MUST give access to the RPC services using a URL addressing scheme to invoke a single RPC via HTTP GET. This encoding can also be used to support RPC execution via form posts including file-uploads as parameters to RPCs. It is recommended that only services that are idempotent are exported in this manner. Containers MUST use the following canonical transformation between the RPC structure and its URL encoding:
This scheme is intentionally verbose and is designed for simple representation of requests with a limited set of parameters. While more compact URL friendly encodings of JSON exist (e.g. [Rison]) this encoding seems more suitable for the intended use case.
Some examples:
This specification has the same requirements as the RESTful API for authentication and authorization. In addition Containers SHOULD also support a request context compatible with use by a gadget running the OpenSocial JS in the containers UI.
When OAuth is used for authorization on a single RPC the OAuth signature uses the URL Addressing (Section 7) representation of the request without the auth param to calculate the signature base string.
We use a hybrid method call syntax to document the operation supported by a service. The types of parameters are described using the same syntax used by the OpenSocial JavaScript documentation. Most parameter defaults are literal values such as "@me" and some are derived from the calling context and have the following definitions:
The following arguments are supported by each of the exposed services. The parameters startIndex and count are interpreted according to [OpenSearch].
| Parameter Name | Description |
|---|---|
| count | The page size for a paged collection. If no parameter is specified the container can choose how many items in the collection should be returned. However, the container SHOULD support a large default count value so that all items can be returned by default. |
| filterBy | For a collection, return entries filtered by the given field name. |
| filterOp | The operation to use when filtering a collection by a field specified in 'filterBy', defaults to "contains". Valid values are 'contains', 'equals', 'startsWith', and 'present'. |
| filterValue | The value to use when filtering a collection. For example, { ... filterBy : name, filterOp : startsWith, filterValue : "John" ...} return all items whose name field starts with John. Johnny and John Doe would both be included.) |
| sortOrder | Can either be 'ascending' or 'descending', defaults to ascending. Used to sort objects in a collection. |
| fields | An array of field names to include in the representation or in the members of a collection. If no fields are specified in the request it is up to the container to decide which fields to return, however, the response MUST always include a minimum set of fields. For people this is [id, name, thumbnailUrl]. For activities this is [id, title]. In place of an array '@all' is accepted to indicate returning all available fields. |
| networkDistance | Modifies group-relative requests (@friends, etc.) to include the transitive closure of all friends up to the specified distance away. MAY NOT be honored by the container. |
| startIndex | Index into a paged collection. |
| updatedSince | When specified the container should only return items whose updated date & time is equal to or more recent then the specified value. The value MUST be a valid [xs:dateTime] (e.g. 2008-01-23T04:56:22Z). |
service-name = "people"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| get | AuthToken auth = HttpRequest.Authorization, String or Array.<String> userId = "@me", String groupId = "@self" | opensocial.Person or Array.<opensocial.Person> |
| create | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@friends", opensocial.Person person | opensocial.Person |
| update | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", opensocial.Person person | opensocial.Person |
| delete | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self" | void |
Retrieve a single person or list of opensocial.Person objects.
All AppData for the person or collection of people returned by a given query may be fetched by specifying the field "appdata" in the fields parameter. If only a subset of AppData fields are desired, they may be fetched by specifying "appdata.<fieldname>" for each field that needs to be fetched.
Example: Fetching all AppData for the requestor:
{
"method":"people.get",
"params": {
"userId":["@me"],
"groupId":"@self",
"params": {
"fields": "appdata"
}
}
}
Example: Fetching the AppData keys "key1", and "key2" for the requestor:
{
"method":"people.get",
"params": {
"userId":["@me"],
"groupId":"@self",
"params": {
"fields": ["appdata.key1", "appdata.key2"]
}
}
}
Support creating opensocial.Activity objects as the targets of a relationship with the specified user, this is a generalization of many use cases including invitation, contact creation etc. Implementation of this method is not required to be OpenSocial compliant.
Support updating the properties of an opensocial.Person object identified by its relationship to the specified user. Implementation of this method is not required to be OpenSocial compliant.
Support removing the relationship between an opensocial.Person and the specified user. Implementation of this method is not required to be OpenSocial compliant.
service-name = "activities"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| get | AuthToken auth = HttpRequest.Authorization, String or Array<String> userId = "@me", String groupId = "@self", String appId = auth.AppId, Array<String> activityIds = [] | opensocial.Activity or Array.<opensocial.Activity> |
| create | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, opensocial.Activity activity | opensocial.Activity |
| update | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, opensocial.Activity activity | opensocial.Activity |
| delete | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, String activityId | void |
Retrieve one or a list of opensocial.Activity objects.
Support creating opensocial.Activity objects as the targets of a relationship with the specified user, the specifically supported use case is posting a new activity to the stream of a single user i.e activity.create(userId="@me",groupId="@self", <some activity>) Support for other parameterizations is not required to be OpenSocial compliant.
Support updating opensocial.Activity objects as the targets of a relationship with the specified user. Implementation of this method is not required to be OpenSocial compliant.
Support removing the relationship between an opensocial.Activity and the specified user. Implementation of this method is not required to be OpenSocial compliant.
service-name = "appdata"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| get | AuthToken auth = HttpRequest.Authorization, String or Array.<String> userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> keys | Map.<String, String> |
| update | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Map.<String, String> data | void |
| delete | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> keys | Map.<String, String> |
Retrieve a map of key-value pairs for the list of specified keys.
Add or replace key-value pairs stored in a users appdata with the key-vaues in the data parameter.
Remove the specifed keys from a users appdata and returned the values associated with those removed keys.
service-name = "messages"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| send | AuthToken auth = HttpRequest.Authorization, String userId = "@me", opensocial.Message message | void |
Sample JSON for Message as one is not defined in the RESTful spec yet.
{
"recipients" : ["example.org:AD38B3886625AAF", "example.org:997638BAA6F25AD"],
"title" : "You have an invitation from Joe",
"body" : "Some content",
"type" : "EMAIL"
}
Send an opensocial.Message object to its defined recipients.
service-name = "albums"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| get | AuthToken auth = HttpRequest.Authorization, String or Array.<String> userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> id | Map.<String, Album> |
| create | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Map.<String, String> data | String |
| update | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Map.<String, String> data | void |
| delete | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> ids | void |
service-name = "mediaItems"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| get | AuthToken auth = HttpRequest.Authorization, String or Array.<String> userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> id | Map.<String, MediaItem> |
| create | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Map.<String, String> data | String |
| update | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Map.<String, String> data | void |
| delete | AuthToken auth = HttpRequest.Authorization, String userId = "@me", String groupId = "@self", String appId = auth.AppId, Array.<String> ids | void |
The cache service is used to manage the resources cached by the container for a given application.
service-name = "cache"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| invalidate | AuthToken auth = HttpRequest.Authorization, Array.<String> invalidationKeys | void |
The system service is used to introspect the endpoint for the set of available services and operations and for metadata about those services.
service-name = "system"
Operations Summary:
| Operation | Parameters | Return |
|---|---|---|
| listMethods | Array.<String> | |
| methodSignatures | String methodName | Signature |
| methodHelp | String methodName | Content |
Containers MUST implement this operation which takes no parameters and returns an array of all methods supported by the endpoint including the system methods. For a container which only supports read access to people and read and create access for activities the result would be:
["people.get", "activities.get", "activities.create", "system.listMethods", "system.methodSignatures", "system.methodHelp"]
Containers MUST implement this operation that returns a method signature describing the types of the parameters, their default values and the type of the return value for a given operation. Note that this scheme does not match that used for XML-RPC definition of method signatures which only specifies a mapping for positional parameters. Type definitions used here match the scheme used in the OpenSocial Javascript API. The example below is the Signature response for people.get.
{
"return" : ["opensocial.Person", "Array.<opensocial.Person>"],
"auth" : {
"default" : null,
"type" : "AuthToken"
},
"userId" : {
"default" : "@me",
"type" : ["String", "Array.<String>"]
},
"groupId" : {
"default" : "@self",
"type" : "String"
},
"fields" : {
"default" : ["id","name","thumbnailUrl","profileUrl"],
"type" : "Array.<String>"
},
"count" : {
"type" : "int",
"required" : false
},
"startIndex" : {
"type" : "int",
"required" : false
}
}
The "return" field indicates the type of the result. If the service can return more than one type then the value is an array of the possible return types. Each named field in the response maps to a parameter of the operation and contains information about the types and values accepted for that parameter. The existence of "default" in the parameter detail indicates the parameter has a default value. The value of a default may be null which means that its not introspectable and usually means that it is some complex derivation. Parameters are assumed to be required unless otherwise indicated by the existence of the "required" field with a value of false.
Containers MAY implement this operation that returns a textual description of the operation identified by the methodName parameter. A container can choose to return either plaintext or HTML as the response.
A common set of error codes are used to represent failure modes that apply generally to operations and so are reserved for use by this specification.
| Code | Meaning |
|---|---|
| -32700 (Parse error) | Invalid JSON. An error occurred on the server while parsing the JSON text. |
| -32600 (Invalid Request) | The received JSON not a valid JSON-RPC or batch of JSON-RPCs. |
| -32601 (Method not found) | The requested remote-procedure does not exist / is not available. |
| -32602 (Invalid params) | Invalid method parameters. |
| -32603 (Internal server error) | Internal server error. |
| -32099..-32000 | Reserved for implementation-defined server-errors. |
| 401 (Unauthorized) | Access token does not grant access to the resource, |
| 404 (Not Found) | The requested resource was not found. |
| 409 (Conflict) | The requested update conflicts with a prior change. |
| 0-1000 | Reserved for future use by this specification. |
Containers MAY choose to implement an optimistic concurrency scheme for updates. If this facility is supported by the container an "etag" field will be embedded in the updatable object when it is retrieved. When the container receives a request to update an object and an etag is present in the request the container MAY choose to deny the request because it has detected a conflicting update. Containers are free to allow updates even in the presence of conflicting etags at their own discretion but SHOULD document when such behavior is allowed.
In situations where containers support the URL Addressing (Section 7) scheme to perform an RPC the container MAY return arbitrary content types in the response other than application/json. The system.methodHelp operation relies on this to provide browseable documentation.
A container MAY choose to support file uploads from browsers to the RPC endpoint and have the uploaded files be passed as parameters to the RPC. This is achieved by browsers posting a form which contains the files to the endpoint and including a form-parameter called "request" which contains the form-encoded RPC request. The form input element names for the uploaded files will be bound to the corresponding parameter names on execution.
| [OpenSearch] | Clinton, D., "OpenSearch 1.1, Draft 3" , November 2008, <http://www.opensearch.org/Specifications/OpenSearch/1.1/Draft_3>. |
| [RFC2119] | Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels" , RFC 2119, March 1997. |
| [RFC2606] | Eastlake, D. and A. Panitz, "Reserved Top Level DNS Names" , RFC 2606, June 1999. |
| [XML-RPC Introspection] | opensocial-and-gadgets-spec@googlegroups.com, , "XML-RPC Introspection" , January 2000, <http://xmlrpc-c.sourceforge.net/introspection.html>. |
| [RESTful Protocol Specification] | social, o., "RESTful Protocol Specification 0.9" , January 2009, <REST-API.html>. |
| [JSON-RPC] | JSON-RPC Working Group, , "JSON-RPC 2.0 Specification proposal" , November 2008, <http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal?pli=1>. |
| [JSON-RPC Error] | JSON-RPC Working Group, , "JSON-RPC Error Object" , November 2008, <http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal?pli=1#error-object>. |
| [Rison] | "Rison - Compact Data in URIs" , <http://mjtemplate.org/examples/rison.html>. |
| [xs:dateTime] | Biron, P.V. and A. Malhotra, "XML Schema Part 2: Datatypes Second Edition" , October 2004, <http://www.w3.org/TR/xmlschema-2/#dateTime>. |
| [OAuth Core 1.0] | Atwood, M., Conlan, R. M., Cook, B., Culver, L., Elliott-McCrea, K., Halff, L., Hammer-Lahav, E., Laurie, B., Messina, C., Panzer, J., Quigley, S., Recordon, D., Sandler, E., Sergent, J., Slesinsky, B., and A. Smith, "OAuth Core 1.0" , December 2007, <http://oauth.net/core/1.0/>. |