Skip to content

Warmshowers RESTful Services for Mobile Apps

Jeremy Calvert edited this page Jun 14, 2017 · 39 revisions

Note that all clients should use the canonical name of the host, www.warmshowers.org, in the URL and also use https. Otherwise the redirect comes back as a 404 error.

CSRF Required with Drupal 7 upgrade in Fall, 2015

Following the upgrade to Drupal 7 all authenticated service calls must contain a valid CSRF header - https://www.drupal.org/node/2012982:

Note that Services clients using session authentication now should supply a special X-CSRF-Token header with a token that can be retrieved from http://example.com/services/session/token. This is needed for writing HTTP methods calls (POST, PUT, DELETE).

ALL apps should now request a service token from https://www.warmshowers.org/services/session/token immediately after login and before any further service requests. (Login does not require a token, as there's no session. But even a logout requires a token because it's a session activity.)

GET /services/session/token
Accept: text/plain

To provide a smooth upgrade path from D6 to D7 this request has been included in D6 and responses to both will look the same except that D6 will return a fake token instead. Example response included below.

Response
Status Code: 200 OK
Content-Type: text/plain
Body: KkSi6vREzVqx_M7Hso52z6u_J9rfKspUF7s3kwxYLZc

Apps must now check for a service token as above and include it as an X-CSRF-Token header if one exists:

POST /services/rest/XXXXXXXXXX
X-CSRF-Token: {token_value}

Login

Logs a specified user in and retrieves its profile data on a successful (first) login via this client.

  • URL

    /services/rest/user/login

  • Method:

    POST

  • Headers

    • Accept: application/json (Optional. Otherwise, XML is sent)
  • URL Params

None

  • Data Params

    to be sent as x-www-form-urlencoded

    Required:

    • username=

    • password=

  • Success Response:

    • Code: 200 OK

      Response: JSON with sessid, session_name, token, and user object.

      This information can then be used to authenticate all following requests by adding the following headers:

      X-CSRF-Token: <token>
      Cookie:       <session_name>=<sessid>
      

      Content: (Example)

      JSON:

      {
        "sessid": "",
        "session_name": "",
        "token": "",
        "user": {
          "uid": "",
          "name": "",
          "theme": "",
          "signature_format": "",
          "created": "",       // Unix timestamp as a String
          "access": "",        // Unix timestamp as a String
          "login": 1460103119, // Unix timestamp as an Integer
          "status": "",
          "timezone": "",
          "language": "",
          "picture": {
            "fid": "",
            "uid": "",
            "filename": "",
            "uri": "",
            "filemime": "",
            "filesize": "",
            "status": "",
            "timestamp": "",
            "url": ""
          },
          "fullname": "",
          "notcurrentlyavailable": "",
          "fax_number": "",
          "mobilephone": "",
          "workphone": "",
          "homephone": "",
          "preferred_notice": "",
          "maxcyclists": "",
          "storage": "",
          "motel": "",
          "campground": "",
          "bikeshop": "",
          "comments": "",
          "shower": "",
          "kitchenuse": "",
          "lawnspace": "",
          "sag": "",
          "bed": "",
          "laundry": "",
          "food": "",
          "languagesspoken": "",
          "URL": "",
          "becomeavailable": "",
          "set_unavailable_timestamp": "",
          "set_available_timestamp": "",
          "last_unavailability_pester": "",
          "hide_donation_status": "",
          "email_opt_out": "",
          "street": "",
          "additional": "",
          "city": "",
          "province": "",
          "postal_code": "",
          "country": "",
          "latitude": "",
          "longitude": "",
          "source": "",
          "comment_notify_settings": {
            "uid": "",
            "node_notify": "",
            "comment_notify": ""
          },
          "privatemsg_disabled": false,
          "profile_image_mobile_profile_photo_std": "",
          "profile_image_profile_picture": "",
          "profile_image_mobile_photo_456": "",
          "profile_image_map_infoWindow": ""
        }
      }

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>
          <sessid></sessid>
          <session_name></session_name>
          <token></token>
          <user>
              <uid></uid>
              <name></name>
              <theme></theme>
              <signature_format></signature_format>
              <created></created>
              <access></access>
              <login></login>
              <status></status>
              <timezone></timezone>
              <language></language>
              <picture>
                  <fid></fid>
                  <uid></uid>
                  <filename></filename>
                  <uri></uri>
                  <filemime></filemime>
                  <filesize></filesize>
                  <status></status>
                  <timestamp></timestamp>
                  <url></url>
              </picture>
              <fullname></fullname>
              <notcurrentlyavailable></notcurrentlyavailable>
              <fax_number></fax_number>
              <mobilephone></mobilephone>
              <workphone></workphone>
              <homephone></homephone>
              <preferred_notice></preferred_notice>
              <maxcyclists></maxcyclists>
              <storage></storage>
              <motel></motel>
              <campground></campground>
              <bikeshop></bikeshop>
              <comments></comments>
              <shower></shower>
              <kitchenuse></kitchenuse>
              <lawnspace></lawnspace>
              <sag></sag>
              <bed></bed>
              <laundry></laundry>
              <food></food>
              <languagesspoken></languagesspoken>
              <URL></URL>
              <becomeavailable></becomeavailable>
              <set_unavailable_timestamp></set_unavailable_timestamp>
              <set_available_timestamp></set_available_timestamp>
              <last_unavailability_pester></last_unavailability_pester>
              <hide_donation_status></hide_donation_status>
              <email_opt_out></email_opt_out>
              <street></street>
              <additional></additional>
              <city></city>
              <province></province>
              <postal_code></postal_code>
              <country></country>
              <latitude></latitude>
              <longitude></longitude>
              <source></source>
              <comment_notify_settings>
                  <uid></uid>
                  <node_notify></node_notify>
                  <comment_notify></comment_notify>
              </comment_notify_settings>
              <privatemsg_disabled></privatemsg_disabled>
              <profile_image_mobile_profile_photo_std></profile_image_mobile_profile_photo_std>
              <profile_image_profile_picture></profile_image_profile_picture>
              <profile_image_mobile_photo_456></profile_image_mobile_photo_456>
              <profile_image_map_infoWindow></profile_image_map_infoWindow>
          </user>
      </result>
  • Error Response:

    • Code: 401 Unauthorized : Wrong username or password.

      Content:

      JSON:

      [ "Wrong username or password." ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>Wrong username or password.</result>
    • Code: 401 Unauthorized : Missing argument username

      Content:

      JSON:

      [ "Missing required argument username" ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>Missing required argument username</result>
    • Code: 401 Unauthorized : Missing argument password

      Content:

      JSON:

      [ "Missing required argument password" ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>Missing required argument password</result>
    • Code: 406 NOT ACCEPTABLE : Already logged in as <username>.

      Content:

      JSON:

      [ "Already logged in as <username>." ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>Already logged in as <username>.</result>

Logout

Logs the currently logged in user out.

  • URL

    /services/rest/user/logout

  • Method:

    POST

  • Headers

    • Accept: application/json (Optional. Otherwise, XML is sent)
    • Cookie: <session_name>=<session_id> (Retrieved during login)
    • X-CSRF-Token: <token> (Also retrieved during login)
  • URL Params

None

  • Data Params

    None

  • Success Response:

    • Code: 200 OK

      Content: (Example)

      JSON:

      [ true ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>1</result>
  • Error Response:

    • Code: 401 Unauthorized : CSRF validation failed

      Content:

      JSON:

      [ "CSRF validation failed" ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>CSRF validation failed</result>
    • Code: 406 NOT ACCEPTABLE : User is not logged in.

      Content:

      JSON:

      [ "User is not logged in." ]

      XML:

      <?xml version="1.0" encoding="utf-8"?>
      <result>User is not logged in.</result>

Search for users by location

POST /services/rest/hosts/by_location
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Post parameters: minlat maxlat minlon maxlon centerlat centerlon limit

Search for users by keyword (city/fullname/username/email)

offset and limit parameters allow paging through the results. The results include a total count.

POST /services/rest/hosts/by_keyword
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Post parameters: keyword offset limit

Retrieve user info

GET /services/rest/user/<UID>
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Get user info (deprecated, 2014-12-11)

GET /user/<uid>/json
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Read feedback for a user

GET /user/<uid>/json_recommendations
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Create feedback

(Note: I had to do drush vset services_node_save_button_trust_referral_resource_create "Submit" to make this work)

POST /services/rest/node
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Post parameters:

node[type]=trust_referral
node[field_member_i_trust][0][uid][uid]=<username>  (NOTE not the uid, the username instead)
node[field_rating][value]=Postive|Neutral|Negative
node[body]=<body of feedback>
node[field_guest_or_host][value]=Guest|Host|Met Traveling|Other
node[field_hosting_date][0][value][year]=<year>
node[field_hosting_date][0][value][month]=<numeric month, 1-12>

Send a new privatemsg (not replying)

POST /services/rest/message/send
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Post parameters:

recipients=comma-delimited set of username(s) [Not UID]
subject=
body=

Privatemsg reply (reply to thread)

POST /services/rest/message/reply
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Parameters thread_id= body=

Message unreadCount

POST /services/rest/message/unreadCount
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Privatemsg get (all)

POST /services/rest/message/get
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Privatemsg getThread (a single thread)

POST /services/rest/message/getThread
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Parameters thread_id=

Privatemsg markThreadRead (or optionally unread)

POST /services/rest/message/markThreadRead
Accept: application/json
Cookie: <session_name>=<sessid>  (obtained from login)

Parameters thread_id= status=[0|1] (OPTIONAL, defaults to 0==READ. 1 means UNREAD)

Drupal 7 Privatemsg Messages API Changes

** experimental, not in effect yet **

Access inbox: GET /services/rest/message

Access a single message: GET /services/rest/message/MESSAGE_ID

Create a new message thread (if thread_id is empty) or reply to a thread (if thread_id is provided) POST /services/rest/message

with

  • body
  • recipients (username)
  • subject
  • thread_id