Last updated February 20, 2013. Created by joe casey on February 20, 2012.
Edited by Nikhil Mohan, jackbravo. Log in to edit this page.


Overview

I set up a Services sandbox at DrupalWhateverServicesSandbox.com. This is a learning tool to help you get up to speed on Drupal Services via JSON/REST. With the instructions here you should be able to access the server to add, view, and update content. Yes, I am inviting you to create users and content on my site. Please be responsible. I will delete users and content from time to time at my discretion.

DrupalWhateverServicesSandbox.com is a new Drupal-7.22 install with just enough added to get Services 7.x-3.1 running. See sandbox server setup for details. It's fairly simple to get your own server running.

It is important that you understand REST concepts. The Drupal Services REST server follows the REST style closely, and you will minimize confusion if you understand why things are done the way they are. See Representational state transfer and RESTful Web services: The basics for a good introduction.

Then read Working with REST Server for a rather terse discussion of how Drupal Services implements REST.

Drupal Services Uses drupal_get_form()

Drupal Services uses drupal_get_form() wherever it can. This means that it accepts the same fields, with the same names, as the forms you see in your browser on a Drupal site. This approach has many advantages:

  • Familiarity
  • Using existing code
  • Allowing all hooks to run, so other modules that work with forms can run just as they do via the browser interface.

There is at least one significant disadvantage to this approach. If you change the fields in a content type, even just the widget, you may have to change the JSON/REST requests you make via Services. In other words, changing a content type may break your services interface.

Requirements For a Successful JSON/REST call

  • Correct URL
  • Correct action: POST, GET, PUT
  • Correct content type: application/json for this discussion
  • Correct JSON
    • Valid JSON format. See www.json.org for the JSON spec.
    • Correct field names
    • Correct values
  • Correct session status
    • Logged in or not, as the situation calls for.
    • Cookie or not, as the situation calls for.
  • Correct server configuration - must enable and permit the desired function.


The HttpRequester Tool

I suggest using the Firefox addon HttpRequester for accessing the sandbox. It is simple to install and use and allows you to focus on the essentials of the JSON/REST activity.

There is another great tool that you can use Postman - REST Client

Here is a short of its features:

  • Compact layout
  • HTTP requests with file upload support
  • Formatted API responses for JSON and XML
  • HATEOS support
  • Image previews
  • Request history
  • Basic Auth and OAuth 1.0 helpers
  • Autocomplete for URL and header values
  • Key/value editors for adding parameters or header values. Works for URL parameters too.
  • Use environment variables to easily shift between settings. Great for testing production, staging or local setups.
  • Keyboard shortcuts to maximize your productivity

Which is also available as a chrome extension https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnp...

HttpRequester has URL, content type, and content fields that precisely match the examples shown below.

However, you can use any interface you prefer, such as a CURL script, an app you are developing, or whatever you like.

One 'gotcha' when using HttpRequester - your HttpRequester session status is shared with the browser. Logging in or out via the browser has the same effect in your HttpRequester session, and vice versa. This is a bug or a feature, depending on what you want to do. During development, I establish a browser session with the server via Chrome, and use the Firefox session only for HttpRequester. This keeps the browser and HttpRequester sessions separate.

Examples

These are URLs that work more or less 'out of the box'. That is, they need configuration and permissions, but no custom coding.

To use these examples with HttpRequester:

  • Copy the URL (the part starting with 'HTTP') into the URL text field.
  • Choose the 'application/json' Content Type from the dropdown menu.
  • Copy the JSON (the braces and everything within them) into the content textarea. Some of the examples do not have any JSON, in that case the content textarea remains empty.
  • Click the GET, POST, or PUT button as appropriate for the example.

We show the 'View raw transaction' output from HttpRequester, with the header info edited out for brevity. For the more complex responses, you can run the JSON part of the response through the JSON formatter at Curious Concept JSON Formatter to improve readability.

After adding or altering a node, you can visit http://drupalwhateverservicessandbox.com/ in your browser to see the results.

I will be adding more examples. If you have your own examples of 'out of the box' functions, please add them as comments and I will work them into the body.

Register a new user

POST http://drupalwhateverservicessandbox.com/rest/user/register
    Content-Type: application/json
    {
        "name":"services_user_1",
        "pass":"password",
        "mail":"services_user_1@example.com"
    }
     -- response --
    200 OK
    Content-Type:  application/json
    {"uid":"2","uri":"http://drupalwhateverservicessandbox.com/rest/user/2"}

Our server is configured to allow visitors to register themselves without an email, so this leaves the new user logged in to the site.

Create an Article

POST http://drupalwhateverservicessandbox.com/rest/node
Content-Type: application/json
{
  "type":"article",
  "title":"Article submitted via JSON REST",
  "body":{
    "und":[
      {
        "value":"This is the body of the article."
      }
    ]
  }
}
-- response --
200 OK
{"nid":"22","uri":"http://drupalwhateverservicessandbox.com/rest/node/22"}

The 'nid' is the node id of the node you just created, and the uri can be used to retrieve the node.

Warning: 200 Response Code Is Not Enough

200 OK - But It Isn't OK

POST http://drupalwhateverservicessandbox.com/rest/node
Content-Type: application/json
{
  "type":"article",
  "title":"Article With No Body",
  "body":{
    "und":[
      {
        "body":"This article body will not be created."
      }
    ]
  }
}
-- response --
200 OK
{"nid":"27","uri":"http://drupalwhateverservicessandbox.com/rest/node/27"}

This request is very similar to the previous example, but it uses "body":"This article body will not be created." when it should use "value":"This article body will not be created.". The request looks like it succeeds, and the article has been created, but the article body is NOT created. You have to test your procedures and make sure they work.

Retrieve an Article

GET http://drupalwhateverservicessandbox.com/rest/node/22
-- response --
200 OK
{"vid":"22","uid":"11","title":"Article submitted via JSON REST","log":"","status":"1","comment":"2","promote":"1","sticky":"0","nid":"22","type":"article","language":"en","created":"1329691988","changed":"1329691988","tnid":"0","translate":"0","revision_timestamp":"1329691988","revision_uid":"11","body":{"und":[{"value":"This is the body of the article.","summary":"","format":"filtered_html","safe_value":"<p>This is the body of the article.</p>\n","safe_summary":""}]},"field_tags":[],"field_image":[],"rdf_mapping":{"field_image":{"predicates":["og:image","rdfs:seeAlso"],"type":"rel"},"field_tags":{"predicates":["dc:subject"],"type":"rel"},"rdftype":["sioc:Item","foaf:Document"],"title":{"predicates":["dc:title"]},"created":{"predicates":["dc:date","dc:created"],"datatype":"xsd:dateTime","callback":"date_iso8601"},"changed":{"predicates":["dc:modified"],"datatype":"xsd:dateTime","callback":"date_iso8601"},"body":{"predicates":["content:encoded"]},"uid":{"predicates":["sioc:has_creator"],"type":"rel"},"name":{"predicates":["foaf:name"]},"comment_count":{"predicates":["sioc:num_replies"],"datatype":"xsd:integer"},"last_activity":{"predicates":["sioc:last_activity_date"],"datatype":"xsd:dateTime","callback":"date_iso8601"}},"cid":"0","last_comment_timestamp":"1329691988","last_comment_name":null,"last_comment_uid":"11","comment_count":"0","name":"services_user_1","picture":"0","data":null,"path":"http://drupalwhateverservicessandbox.com/node/22"}

Note that the URL in this case is the one that was returned by the previous request, when the article was created. Drupal Services returns a URL to the created resource whenever possible.

Comment On an Article

POST http://drupalwhateverservicessandbox.com/rest/comment
Content-Type: application/json
{
  "nid":28,
  "subject":"Comment submitted via JSON REST",
  "comment_body":{
    "und":[
      {
        "value":"This is a great article."
      }
    ]
  }
}
-- response --
200 OK
{"cid":"1","uri":"http://drupalwhateverservicessandbox.com/rest/comment/1"}

Please note: comments have been disabled on DrupalWhateverServicesSandbox.com due to spam.

You need to know the nid (node id) of the article to add a comment. This may or may not be be in the URL of the article.

Create a Page

POST http://drupalwhateverservicessandbox.com/rest/node
Content-Type: application/json
{
  "type":"page",
  "title":"Page submitted via JSON REST",
  "body":{
    "und":[
      {
        "value":"This is the body of the page."
      }
    ]
  }
}
-- response --
200 OK
{"nid":"24","uri":"http://drupalwhateverservicessandbox.com/rest/node/24"}

Note that the page is created, but does not show up in a menu on the site. Adding the menu settings is quite a bit more complicated. However, I've configured the Basic Page content type to be shown in the list of new nodes on the front page, so you can see the page there.

Alter an Article

PUT http://drupalwhateverservicessandbox.com/rest/node/22
Content-Type: application/json
{
    "type":"article",
    "title":"Change the Title of an Article"
}
-- response --
200 OK
{"nid":"22","uri":"http://drupalwhateverservicessandbox.com/rest/node/22"}

Note that this is a PUT. We are updating an existing resource, in the REST approach this requires a PUT.

Get All Nodes

GET http://drupalwhateverservicessandbox.com/rest/node
-- response --
200 OK
[{"nid":"27","vid":"27","type":"article","language":"en","title":"Article With No Body","uid":"11","status":"1","created":"1329692914","changed":"1329692914","comment":"2","promote":"1","sticky":"0","tnid":"0","translate":"0","uri":"http://drupalwhateverservicessandbox.com/rest/node/27"},{"nid":"26","vid":"26","type":"article","language":"en","title":"Article submitted via JSON REST","uid":"11","status":"1","created":"1329692842","changed":"1329692842","comment":"2","promote":"1","sticky":"0","tnid":"0","translate":"0","uri":"http://drupalwhateverservicessandbox.com/rest/node/26"},{"nid":"24","vid":"24","type":"page","language":"en","title":"Page submitted via JSON REST","uid":"11","status":"1","created":"1329692316","changed":"1329692316","comment":"1","promote":"1","sticky":"0","tnid":"0","translate":"0","uri":"http://drupalwhateverservicessandbox.com/rest/node/24"},{"nid":"22","vid":"22","type":"article","language":"en","title":"Change the Title of an Article","uid":"11","status":"1","created":"1329691988","changed":"1329692495","comment":"2","promote":"1","sticky":"0","tnid":"0","translate":"0","uri":"http://drupalwhateverservicessandbox.com/rest/node/22"},{"nid":"4","vid":"4","type":"page","language":"und","title":"Sandbox Server Setup","uid":"1","status":"1","created":"1328999413","changed":"1329591136","comment":"1","promote":"0","sticky":"0","tnid":"0","translate":"0","uri":"http://drupalwhateverservicessandbox.com/rest/node/4"}]


Logout a user

POST http://drupalwhateverservicessandbox.com/rest/user/logout
    Content-Type: application/json
     -- response --
    200 OK
    Content-Type:  application/json
    true

This is a POST, even though there is no JSON content, because it changes the state of the server.

JSON structure for Drupal Form Fields

You can figure out the required structure of the JSON request by looking at the form for the action you want to do.

For example, to add an article, navigate in your browser to node/add/article and examine the source code of the form. Find the fields you want to fill in. (I've edited out all extraneous material):

  • &lt;input type="text" name="title" value="" /&gt;
  • &lt;textarea name="body[und][0][value]" &gt;

The 'title' field is easy. The corresponding JSON is

    "title":"Title Text Goes Here",

The 'body' field is not so simple. body[und][0][value] indicates an array that the Drupal form API is using to store the body and related information. For example, the [und] component indicates the language ('und' corresponds to LANGUAGE_NONE). There are other parts of the array that we can ignore.

Here is a no-guessing way to get from body[und][0][value] to a JSON expression. Write a little PHP script that expresses body[und][0][value] as an array, then json_encode it. Here's a complete script that creates the whole expression.:

<?php
$request
= array(
 
'type' => 'article',
 
'title' => 'Article submitted via JSON REST services',
 
// body[und][0][value]
 
'body' => array('und' => array( 0 => array('value' => 'This is the body of the article.'))),
);
echo
json_encode($request);
?>

Here is the output of the script:

{"type":"article","title":"Article submitted via JSON REST services","body":{"und":[{"value":"This is the body of the article."}]}}

Note that our JSON request includes the "type":"article" field although the form did not have one. The form actually does include this information, however, in the action="/node/add/article" parameter of the form tag.

JSON/REST Error Codes and How to Interpret Them

I'm skipping obvious errors, like:

    401 Unauthorized: Wrong username or password.
    406 Not Acceptable: E-mail address field is required.
    406 Not Acceptable: The e-mail address &lt;em class="placeholder">services_user_1@example.com&lt;/em> is already registered. &lt;a href="/user/password">Have you forgotten your password?&lt;/a>

Less obvious errors

0

POST http://drupalwhateverservicessandbox/rest/comment
Content-Type: application/json
{
  "nid":28,
  "subject":"Comment submitted via JSON REST",
  "comment_body":{
    "und":[
      {
        "value":"This is a great article."
      }
    ]
  }
}
-- response --
0

The URL is missing the .com. The request never reaches the server.

404 Not found: Could not find resource register.

    POST http://drupalwhateverservicessandbox.com/rest/register
        404 Not found: Could not find resource register.

The URL should be rest/user/register, not rest/register.

406 Not Acceptable: Unsupported request content type text/xml

    406 Not Acceptable: Unsupported request content type text/xml

Should be Content-Type: application/json

401 Unauthorized: Missing required argument node

POST http://drupalwhateverservicessandbox.com/rest/node
Content-Type: application/json
{
  "type":"page",
  "title":"Page submitted via JSON and REST services",
  "body":{
    "und":[
      {
        "value":"This is the body of the page."
      }
    ]
  }
-- response --
401 Unauthorized: Missing required argument node

This unhelpful message is occurring because of invalid JSON - the closing brace is missing.

401 Unauthorized: Access denied for user test_user_2

    POST http://drupalwhateverservicessandbox.com/rest/user/register
        Content-Type: application/json
        {
            "name":"services_user_6",
            "pass":"test_password",
            "mail":"services_user_6@example.com"
        }
         -- response --
        401 Unauthorized: Access denied for user test_user_2
        null

Can't register a new user while already logged in.

401 Unauthorized: Access denied for user services_user_6

    POST http://drupalwhateverservicessandbox.com/rest/node
      Content-Type: application/json
      {
          "type":"article",
          "title":"Article submitted via Services",
          "body":"Here is the body of an article submitted via Services."
      }
       -- response --
      401 Unauthorized: Access denied for user services_user_6

The server has not allowed the 'Article: Create new content' permission for authenticated users.

404 Not found: Could not find the controller.

POST http://drupalwhateverservicessandbox.com/rest/node/3
Content-Type: application/json
{
    "type":"page",
    "title":"Basic Page submitted via Services",
    "body":"Here is the body of a page submitted via Services."
}
-- response --
404 Not found: Could not find the controller.

This is not a proper REST request and does not make sense to Drupal Services. By using POST with node/3, the request is, in effect, asking to create a node within a node, something Drupal Services does not have a method for, hence the 404.

406 Not Acceptable: Missing node type

PUT http://drupalwhateverservicessandbox.com/rest/node/3
Content-Type: application/json
{
    "title":"Change the Title of a Basic Page"
}
-- response --
406 Not Acceptable: Missing node type
null

The node type field is required here. This is reasonable but not intuitively obvious.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

Thank you, thank you, thank you. This is the best tutorial that I have come across. I have spent two days trying to get my Services JSON server to work and your tutorial answered many questions, and I fully understand this now. Excellent examples..

I have been trying all day to figure out how to change a user's password. Any help appreciated.

To update user 10..
Use PUT with url http://mysite.com/services/user/10
application/json
Argument is "data"

So the json to pass would look like
{"data":{"pass":"Jacko"}}
to change user 10's password to Jacko..

If you are using application/x-www-form-urlencoded
then the data to pass would look like this
&data[pass]=Jacko

For a full list of all possible options..
https://gist.github.com/affc9864487bb1b9c918

I was just digging through the user resource code to figure out the API and was coming here to try to document what I found, but that link does it nicely.

It must be indicative of something besides the redistribution of wealth...

He doesn't mention register, which I was curious about because it's in the resource list in addition to "create". But the code says it's essentially an alias, except the URL ends with "/.../user/register" instead of just "/.../user".

...which kind-of makes one curious why it was added, but whatever. ;)

It must be indicative of something besides the redistribution of wealth...

I see an example for logging out, but does anyone have an example for logging in?

Thanks

POST http://drupalwhateverservicessandbox.com/rest/user/login
Content-Type: application/json
{
"username":"services_user_1",
"password":"password"
}

-- response --
200 OK
and a lot more letters :)

Thanks for the login example, Is there an example for file resource how it will be if we wanted to post it by Poster or httpRequester? I followed this http://drupanium.org/api/82 but it gives me 401 Unauthorized: Missing required argument file.

Thanks,

omar-swe

HttpRequester allows you to point to a json file, which is convenient for testing and development.

Here are the contents of a json input file:
{"filesize":2511576,"filename":"photo.JPG","status":1,"uid":"0","file":"\/9j\/4T\/+RXhpZgAATU0AK ...<megabytes of base-64 encoded image material omitted>...4xdmf\/Z"}

"PUT" that to the file resource, probably something like /rest/file

If successful you will get back a file id.

In my environment the uid used in the json must match the uid of the logged in user issuing the PUT.

Where you able to update the user picture using JSON / REST?

I worked though this as a test case some time ago. You can always upload a photo and get a fid back. You can also supply updated user info with a user-picture fid. So, you can add or refresh a user picture, but it's a multi-step process, and the picture may not be in the same directory as other user pictures. I'm not giving details here because it's not a good solution.

hi, i have used the above code successfully for image uploads but for video files the base64 conversion doesn't work. have you uploaded videos before please share?

David

Hi davidserene,

Do you have any solution for uploading a audio or video file. I am searching in web for the same issue for last 2 weeks. Kindly help me.

Thanks in advance.

I am using Service 6.x-3.3.I am using HttpRequester to test the user login.
I am able to login into the site http://stage.myast.org/ using xdeveloper/xdeveloper.
But using HttpRequester tool , I am not able to login into the site.

The below are the inputs i have given.

URL : http://stage.myast.org/astapi/user/login.json
Request Type : POST
Content Type : application/json
Content : {"username":"xdeveloper","password":"xdeveloper"}

After submitting this request , i am getting the below response.

POST on http://stage.myast.org/astapi/user/login.json
Status : 401 Unauthorized: Wrong username or password.

Could you provide me a solution for this?

vnpadmaja

any suggestions on how to post a photo as a service? I was thinking of posting from an iPHONE and ANDROID application. If you give me a hint, I'll post sample iOS code.

Doing Native iPHONE, ANDROID, Titanium, node.js and DRUPAL. as a contractor.

You can make a multiple-step Drupal process into a one-step user process. The user takes a photo and clicks 'upload'. Your app uploads the photo as a file, then calls a services helper module (that you write), passing in the fid you just got. The module puts the image into some image collection that you have created.

I haven't discussed the 'services helper module' topic on this page, but Services is built with them in mind, and they are fairly straightforward to write and call. Creating a resource for Services 3.x is a good example. It has more than you need if you are using pre-existing content types.

In regards to the first example with a response 0, is there a reason why I can't send anything without it being a .com, .net, .org or et cetera? Is this a limitation of Drupal or something in the W3C spec?

The examples use a .com domain, but the user has omitted the .com in this case. This can't work. It's like trying to get to Drupal.org by typing 'drupal'. There's nothing special or restrictive with regards to Services. If the address can be resolved, the request can be delivered. If not, not.

I tried to update a node's field (text list with radiobutton widget) with the use of JSON with application/json content type and with a PUT method and all I get is 406 unacceptable:

{"form_errors":{"field_order_status][und":"An illegal choice has been detected. Please contact the site administrator."}}

I tried to change und to something else like 'en' and it submits ok, but wouldn't update the said field

Hi,

I've been searching for quite some time the usual sources for something about "requesting" a password reset through email or username.

I have tried many restpoints... but nothing came up so far (did the same trial and error for other stuff but this one is not working well...).

We should really register in one place all the default resources rest point....

Thanks,
NR

Lavuolp - www.lavuolp.ch