NAV Navbar
ControlShift Labs

Introduction

Welcome to the ControlShift Labs Developer documentation. Our APIs are designed to allow software engineers to create rich integrations between ControlShift content and third-party services and websites.

We offer several APIs to customers to cover different integration use cases.

JSON API Endpoints

The JSONP API is a simple way to embed ControlShift petition content in external sites. It’s intended for use by a front-end developer embed content on web pages outside of the platform. For example, a developer could:

All of the endpoints can be consumed as JSONP instead of JSON by adding callback or variable parameters to the URLs.

The URL slugs through the API are the same as those that are used through the web to represent specific petitions or categories. Many front-end libraries including jQuery make it easy to consume JSONP endpoints. Our examples below use jQuery to consume these resources.

Get a single petition

$(document).ready(function(){
  var petitionSlug = 'repair-the-yellow-brick-road-1';
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/petitions/'+petitionSlug+'.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would display your returned petition data in the console. The JSON response data would be structured like this:

{
  "id": 92283,
  "title": "Repair the Yellow Brick Road",
  "slug": "repair-the-yellow-brick-road-1",
  "who": "Oz, the Great and Terrible, Wizard",
  "what": "The Yellow Brick Road has been neglected; commit to repairing the damaged sections of the road in the next year!",
  "why": "The Yellow Brick Road is the main road connecting Munchkin Country to the Emerald City and in its current state it's impassable.",
  "created_at": "2014-10-02T01:43:17Z",
  "updated_at": "2018-05-07T15:38:39Z",
  "delivery_details": "We're off to see the wizard!",
  "administered_at": "2018-03-21T19:05:54Z",
  "source": "homepage",
  "alias": null,
  "bsd_constituent_group_id": null,
  "categories": [
    {
      "name": "Oz",
      "slug": "oz"
    }
  ],
  "goal": 500,
  "effort": null,
  "partnership": null,
  "group": null,
  "resized_image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
  "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
  "creator_name": "Kristyn Arrighi",
  "signature_count": 223
  "location": {
    "latitude":"36.1946827",
    "longitude":"-78.8087571",
    "query":"Land of Oz",
    "venue":"Land of OZ",
    "street_number":"1007",
    "street":"Beech Mountain Pkwy",
    "street_address":"1007 Beech Mountain Pkwy",
    "locality":"Beech Mountain",
    "region":"NC",
    "postal_code":"28604",
    "country":"US",
    "country_code":"US",
    "country_name":"United States",
    "static_map_url":"https://d8s293fyljwh4.cloudfront.net/locations/static_maps/27575/27575-static-map.png?1532035340"
  },
}

Note: The “group” key is deprecated and will be removed soon. Code should be updated to use the “partnership” key instead.

This retrieves a single petition object.

HTTP Request

GET https://demo.controlshiftlabs.com/petitions/<slug>.json

Query Parameters

Parameter Default Description
slug null string - required - The petition’s unique identification slug. If none is provided, you will get a 404 error. Note: submitted as a part of the endpoint path, not as a separate URL parameter

Working Example

View and edit a working example on codepen.io:

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/petitions/featured.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would display your returned featured petitions data in the console. The JSON would be structured like this:

{
  "meta": {
    "total_pages": 3,
    "total_entries": 25,
    "per_page": 10
  },
  "data": [
    {
      "slug": "repair-the-yellow-brick-road-1",
      "url": "http://demo.controlshiftlabs.com/petitions/repair-the-yellow-brick-road-1",
      "title": "Repair the Yellow Brick Road",
      "who": " Oz, the Great and Terrible, Wizard",
      "what": "The Yellow Brick Road has been neglected; commit to repairing the damaged sections of the road in the next year!",
      "why": "The Yellow Brick Road is the main road connecting Munchkin Country to the Emerald City and in its current state it's impassable.",
      "admin_status": "good",
      "signature_count": 223,
      "goal": 500,
      "creator_name": "Kristyn Arrighi",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2014-10-02T01:43:17Z",
      "updated_at": "2018-05-07T15:38:39Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
      "additional_image_sizes_url": [
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/form/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/horizontal/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/open_graph/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        }
      ],
      "target": {
        "name": " Oz, the Great and Terrible",
        "slug": "oz-the-great-and-terrible",
        "context": "Wizard",
        "location": {
          "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
          "latitude": "42.9399637",
          "longitude": "-78.8087571",
          "street": "Kensington Ave",
          "postal_code": "",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      },
      "location": {
        "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
        "latitude": "42.9399637",
        "longitude": "-78.8087571",
        "street": "Kensington Ave",
        "postal_code": "",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "",
        "created_at": "2016-06-10T19:13:19Z"
      }
    },
    {
      "slug": "we-need-more-bike-lanes",
      "title": "We Need More Bike Lanes",
      "who": "My Mayor",
      "what": "Build the bike lanes",
      "why": "Cyclists aren't safe.",
      "admin_status": "good",
      "signature_count": 5,
      "goal": 100,
      "creator_name": "Greg Dutcher",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2016-08-12T18:08:32Z",
      "updated_at": "2016-09-14T12:43:45Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/167492/original/IMG_1854.JPG?1472046912",
      "additional_image_sizes_url": [
      ]
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/167492/original/IMG_1854.JPG?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/167492/form/IMG_1854.JPG?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/167492/horizontal/IMG_1854.JPG?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/167492/open_graph/IMG_1854.JPG?1473884741"
        }
      ],
      "location": {
        "query": "New York",
        "latitude": "40.6974034",
        "longitude": "-74.1197633",
        "street": "",
        "postal_code": "",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "New York",
        "created_at": "2017-04-11T13:32:08Z"
      }
    }
  ],
  "links": {
    "self": "https://demo.controlshiftlabs.com/petitions/featured.json?page=1",
    "first": "https://demo.controlshiftlabs.com/petitions/featured.json",
    "prev": null,
    "next": "https://demo.controlshiftlabs.com/petitions/featured.json?page=2",
    "last": "https://demo.controlshiftlabs.com/petitions/featured.json?page=3"
  }
}

This retrieves a JSON object compliant with the JSON API specification, containing an array of featured petitions objects on its data property.

Important Note: The previous featured petitions API endpoint at https://demo.controlshiftlabs.com/featured.json (note the missing /petitions path) is being deprecated and will be removed in the near future. Please use this new endpoint for retrieving featured petitions from now on.

HTTP Request

GET https://demo.controlshiftlabs.com/petitions/featured.json

Query Parameters

Parameter Default Description
locale null string - optional - Locale filter for petitions

Working Example

View and edit a working example on codepen.io:

Get list of categories

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/categories.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would display a list of categories in the console. The JSON would be structured like this:

[
  {
    "category_name": "Education",
    "category_count": 18,
    "slug": "education-9",
    "url": "https://demo.controlshiftlabs.com/categories/education-9.json",
    "signature_count": 421,
    "locales": {
      "es": "EducaciĆ³n"
    }
  },
  {
    "category_name": "Environment",
    "category_count": 352,
    "slug": "environment-6",
    "url": "https://demo.controlshiftlabs.com/categories/environment-6.json",
    "signature_count": 2606,
    "locales": {
      "es": "Medio Ambiente"
    }
  }
]

This retrieves a JSON array of category objects.

HTTP Request

GET https://demo.controlshiftlabs.com/categories.json

Working Example

View and edit a working example on codepen.io:

List petitions in a category

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/categories/oz.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would return petitions data from the category with the slug oz. The JSON would be structured like this:

{
  "current_page": 1,
  "total_pages": 25,
  "previous_page": null,
  "next_page": 2,
  "name": "Oz",
  "results": [
    {
      "slug": "let-dorothy-go-home",
      "url": "http://demo.controlshiftlabs.com/petitions/let-dorothy-go-home",
      "title": "Let Dorothy Go Home!",
      "who": " Oz, the Great and Terrible, Wizard",
      "what": "Let poor Dorothy Gale go home to Kansas",
      "why": "There's no place like home.",
      "admin_status": "good",
      "signature_count": 123,
      "goal": 200,
      "creator_name": "Tin Man",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2015-12-02T01:43:17Z",
      "updated_at": "2017-05-07T15:38:39Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
      "additional_image_sizes_url": [
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/form/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/horizontal/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/open_graph/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        }
      ],
      "target": {
        "name": " Oz, the Great and Terrible",
        "slug": "oz-the-great-and-terrible",
        "context": "Wizard",
        "location": {
          "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
          "latitude": "42.9399637",
          "longitude": "-78.8087571",
          "street": "Kensington Ave",
          "postal_code": "",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      }
    },
    {
      "slug": "repair-the-yellow-brick-road-1",
      "url": "http://demo.controlshiftlabs.com/petitions/repair-the-yellow-brick-road-1",
      "title": "Repair the Yellow Brick Road",
      "who": " Oz, the Great and Terrible, Wizard",
      "what": "The Yellow Brick Road has been neglected; commit to repairing the damaged sections of the road in the next year!",
      "why": "The Yellow Brick Road is the main road connecting Munchkin Country to the Emerald City and in its current state it's impassable.",
      "admin_status": "good",
      "signature_count": 223,
      "goal": 500,
      "creator_name": "Kristyn Arrighi",
      "locale": "en",
      "created_at": "2014-10-02T01:43:17Z",
      "updated_at": "2018-05-07T15:38:39Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
      "additional_image_sizes_url": [
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/form/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/horizontal/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/open_graph/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        }
      ],
      "target": {
        "name": " Oz, the Great and Terrible",
        "slug": "oz-the-great-and-terrible",
        "context": "Wizard",
        "location": {
          "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
          "latitude": "42.9399637",
          "longitude": "-78.8087571",
          "street": "Kensington Ave",
          "postal_code": "",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      },
      "location": {
        "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
        "latitude": "42.9399637",
        "longitude": "-78.8087571",
        "street": "Kensington Ave",
        "postal_code": "",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "",
        "created_at": "2016-06-10T19:13:19Z"
      }
    }
  ]
}

This retrieves a paginated list of petitions in a category.

HTTP Request

GET https://demo.controlshiftlabs.com/categories/<category slug>.json

Query Parameters

Parameter Default Description
category slug null string - required - submitted as a part of the endpoint path, not as a separate URL parameter
page 1 integer - optional - The page number of results for the specified category. Minimum of 1.

Working Example

View and edit a working example on codepen.io:

List petitions in a partnership

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/partnerships/our-awesome-partner/petitions.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would return petitions data from the partnership with the slug our-awesome-partner. The JSON would be structured like this:

{
  "current_page": 1,
  "total_pages": 25,
  "previous_page": null,
  "next_page": 2,
  "name": "Oz",
  "results": [
    {
      "slug": "let-dorothy-go-home",
      "url": "http://demo.controlshiftlabs.com/petitions/let-dorothy-go-home",
      "title": "Let Dorothy Go Home!",
      "who": " Oz, the Great and Terrible, Wizard",
      "what": "Let poor Dorothy Gale go home to Kansas",
      "why": "There's no place like home.",
      "admin_status": "good",
      "signature_count": 123,
      "goal": 200,
      "creator_name": "Tin Man",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2015-12-02T01:43:17Z",
      "updated_at": "2017-05-07T15:38:39Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
      "additional_image_sizes_url": [
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/form/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/horizontal/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/open_graph/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        }
      ],
      "target": {
        "name": " Oz, the Great and Terrible",
        "slug": "oz-the-great-and-terrible",
        "context": "Wizard",
        "location": {
          "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
          "latitude": "42.9399637",
          "longitude": "-78.8087571",
          "street": "Kensington Ave",
          "postal_code": "",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      }
    },
    {
      "slug": "repair-the-yellow-brick-road-1",
      "url": "http://demo.controlshiftlabs.com/petitions/repair-the-yellow-brick-road-1",
      "title": "Repair the Yellow Brick Road",
      "who": " Oz, the Great and Terrible, Wizard",
      "what": "The Yellow Brick Road has been neglected; commit to repairing the damaged sections of the road in the next year!",
      "why": "The Yellow Brick Road is the main road connecting Munchkin Country to the Emerald City and in its current state it's impassable.",
      "admin_status": "good",
      "signature_count": 223,
      "goal": 500,
      "creator_name": "Kristyn Arrighi",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2014-10-02T01:43:17Z",
      "updated_at": "2018-05-07T15:38:39Z",
      "image_url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/hero/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741",
      "additional_image_sizes_url": [
        {
          "style": "original",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/original/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "form",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/form/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "horizontal",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/horizontal/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        },
        {
          "style": "open_graph",
          "url": "https://d8s293fyljwh4.cloudfront.net/petitions/images/92283/open_graph/2016-06-20-1466458252-1098096-ywllowbrickroad.jpg?1473884741"
        }
      ],
      "target": {
        "name": " Oz, the Great and Terrible",
        "slug": "oz-the-great-and-terrible",
        "context": "Wizard",
        "location": {
          "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
          "latitude": "42.9399637",
          "longitude": "-78.8087571",
          "street": "Kensington Ave",
          "postal_code": "",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      },
      "location": {
        "query": "Land of Oz, Kensington Avenue, Buffalo, NY, United States",
        "latitude": "42.9399637",
        "longitude": "-78.8087571",
        "street": "Kensington Ave",
        "postal_code": "",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "",
        "created_at": "2016-06-10T19:13:19Z"
      }
    }
  ]
}

This retrieves a paginated list of petitions in a partnership.

HTTP Request

GET https://demo.controlshiftlabs.com/partnerships/<partnership slug>/petitions.json

Query Parameters

Parameter Default Description
partnership slug null string - required - submitted as a part of the endpoint path, not as a separate URL parameter
page 1 integer - optional - The page number of results for the specified partnership. Minimum of 1.

Working Example

View and edit a working example on codepen.io:

List petitions in an effort

$(document).ready(function(){
  var effortSlug = 'drivers-licenses-for-all';
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/efforts/'+effortSlug+'.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would return petitions data from the effort with the slug drivers-licenses-for-all. The JSON would be structured like this:

{
  "title": "Drivers' Licenses for All",
  "slug": "drivers-licenses-for-all",
  "description": "We're asking towns and villages across New York to pass resolutions in support of the Drivers' License bill. Anyone who can pass a driving test should be able to get a license.",
  "goal": 1000,
  "signature_count": 511,
  "petitions": [
    {
      "slug": "ossining-support-drivers-licenses-for-all",
      "url": "http://demo.controlshiftlabs.com/petitions/ossining-support-drivers-licenses-for-all",
      "title": "Ossining: Support Drivers' Licenses For All",
      "who": "Ossining Village Board of Trustees",
      "what": "Pass a resolution in support of the Drivers' Licenses bill",
      "why": "Undocumented immigrants need access to drivers' licenses so they can drive to work, school, and everywhere else. This will improve road safety for everyone.",
      "admin_status": "good",
      "signature_count": 223,
      "goal": 500,
      "creator_name": "Jacinda Moore",
      "locale": "en",
      "successful": false,
      "ended": false,
      "created_at": "2016-10-02T01:43:17Z",
      "updated_at": "2016-10-07T15:38:39Z",
      "target": {
        "name": "Ossining Village Board of Trustees",
        "slug": "ossining-village-board-of-trustees",
        "context": "",
        "location": {
          "query": "Ossining",
          "latitude": "41.1617921",
          "longitude": "-73.8874165",
          "street": "",
          "postal_code": "10562",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      },
      "location": {
        "query": "Ossining",
        "latitude": "41.1617921",
        "longitude": "-73.8874165",
        "street": "",
        "postal_code": "10562",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "",
        "created_at": "2016-06-10T19:13:19Z"
      }
    },
    {
      "slug": "yonkers-support-drivers-licenses-for-all",
      "url": "http://demo.controlshiftlabs.com/petitions/yonkers-support-drivers-licenses-for-all",
      "title": "Yonkers: Support Drivers' Licenses For All",
      "who": "Yonkers City Council",
      "what": "Pass a resolution in support of the Drivers' Licenses bill",
      "why": "Undocumented immigrants need access to drivers' licenses so they can drive to work, school, and everywhere else. This will improve road safety for everyone.",
      "admin_status": "good",
      "signature_count": 300,
      "goal": 500,
      "creator_name": "Jacinda Moore",
      "locale": "en",
      "successful": true,
      "ended_story": "We did it, we won!",
      "ended": true,
      "ended_type": "won",
      "ended_reason": "Campaigner said they won the campaign",
      "created_at": "2016-10-02T01:43:17Z",
      "updated_at": "2016-10-07T15:38:39Z",
      "target": {
        "name": "Yonkers City Council",
        "slug": "yonkers-city-council",
        "context": "",
        "location": {
          "query": "Yonkers",
          "latitude": "40.9443748",
          "longitude": "-73.8993138",
          "street": "",
          "postal_code": "10701",
          "country": "US",
          "region": "NY",
          "street_number": "",
          "venue": "",
          "created_at": "2016-06-10T19:13:19Z"
        }
      },
      "location": {
        "query": "Yonkers",
        "latitude": "40.9443748",
        "longitude": "-73.8993138",
        "street": "",
        "postal_code": "10701",
        "country": "US",
        "region": "NY",
        "street_number": "",
        "venue": "",
        "created_at": "2016-06-10T19:13:19Z"
      }
    }
  ]
}

This retrieves a list of petitions in an effort.

Ended petitions will have the ended attribute set to true and will also include the following additional attributes:

Additionally, successful petitions will have the successful attribute set to true and will include the following additional attribute:

HTTP Request

GET https://demo.controlshiftlabs.com/efforts/<effort slug>.json

Parameter Default Description
effort slug null string - required - submitted as a part of the endpoint path, not as a separate URL parameter

Working Example

View and edit a working example on codepen.io:

Find nearby petitions

$(document).ready(function(){
  var effortSlug = 'drivers-licenses-for-all',
      latitude = 38.88233,
      longitude = -77.17109;
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/efforts/'+effortSlug+'/'+'lookup/query.json',
    dataType: 'jsonp',
    data: {
      'location[latitude]': latitude,
      'location[longitude]': longitude
    }
  })
  .done(function( data ) {
    console.log(data);
  });
});

The above code would return petitions, decision makers, or objectives data from the effort with the slug drivers-licenses-for-all. If returning decision makers, the JSON would be structured like this:

{
  "closest_target": {
    "slug": "ossining-village-board-of-trustees",
    "name": "Ossining Village Board of Trustees",
    "context": "",
    "location": "Ossining, NY",
    "status": "target_petition_created",
    "petition": {
      "slug": "ossining-support-drivers-licenses-for-all",
      "title": "Ossining: Support Drivers' Licenses For All",
      "url": "http://demo.controlshiftlabs.com/petitions/ossining-support-drivers-licenses-for-all",
      "who": "Ossining Village Board of Trustees",
      "signature_count": 223,
      "goal": 500,
      "created_at": "2016-10-02T01:43:17Z",
      "updated_at": "2016-10-07T15:38:39Z",
    }
  },
  "other_targets": [
    {
      "slug": "yorktown-town-council",
      "name": "Yorktown Town Council",
      "context": "",
      "location": "Yorktown, NY",
      "status": "target_awaiting_petition",
      "create_petition_url": "https://demo.controlshiftlabs.com/efforts/drivers-licenses-for-all/petitions/creating?target_id=1234"
    }
  ]
}

Search for closest petition in an effort

This JSON endpoint allows you to reproduce the “search for the nearest petition in an effort” interface. Use this endpoint to build a place for users to enter their location. Once they’ve searched for a location, we’ll return the nearest petition. This endpoint can most easily be used with a Google Maps integration to help your users locate their addresses or other points of interest. For an example, click on the working example below.

HTTP Request

GET https://demo.controlshiftlabs.com/efforts/<effort slug>/lookup/query.json

Query Parameters

This endpoints accepts a few nested attributes in a location object, as described below.

Parameter Default Description
effort slug null string - required - submitted as a part of the endpoint path, not as a separate URL parameter
location[latitude] null float - required - a float representing the queried latitude
location[longitude] null float - required - a float representing the queried longitude
location[country] null string - optional - for efforts configured to use the “by country” search strategy, you can pass in the target country as a two letter ISO 3166-1 code.
location[region] null string - optional - for efforts configured to use the “by state” search strategy, you can pass in the target state as a two letter state abbreviation.

Working Example

View and edit a working example on codepen.io:

Get calendar data

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/calendars/national-day-of-action-against-fracking.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would display a calendar’s properties the js console. The JSON would be structured like this:


{
   "calendar":{
      "name":"National Day of Action Against Fracking",
      "description":"This is the description. It will be great.",
      "slug":"national-day-of-action-against-fracking",
      "image_url":"https://d8s293fyljwh4.cloudfront.net/calendars/images/26/hero/Oak-tree-in-field-007.jpg?1450370489",
      "event_count":85,
      "attendee_count":19,
      "events_upcoming":10
   }
}

This retrieves a JSON object representing a calendar.

HTTP Request

GET https://demo.controlshiftlabs.com/calendars/slug.json

Where slug is the slug of the calendar you’re retrieving.

Get organizing locations: points

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/api/local/points.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would retrieve point locations and log them to the js console. The JSON would be structured like this:

[
    {
        "lat": 40.7032775,
        "lon": -74.0170279,
        "type": "Event",
        "id": "picnic-in-the-park"
    },
    {
        "lat": 8.2710078,
        "lon": 148.0209055,
        "type": "Group",
        "id": "default-org-mariana-trench"
    },
    {
        "lat": 41.467116,
        "lon": -73.9992597,
        "type": "ExternalEvent",
        "id": "https://example.com/sail-on-the-hudson"
    }
]

This JSON endpoint returns a complete list of latitude/longitude coordinates for publicly listed events, groups, and external events in your organisation. It’s intended to be used for plotting organizing activities on a map.

HTTP Request

GET https://demo.controlshiftlabs.com/api/local/points.json

Get organizing locations: details

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/api/local.json?page=1&per_page=3',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would retrieve one page of location data and log it to the js console. The JSON would be structured like this:

{
    "meta": {
        "count": 23,
        "page": 1,
        "per_page": 3,
        "total_pages": 8
    },
    "data": [
        {
            "type": "Event",
            "id": "picnic-in-the-park",
            "title": "Picnic in the Park",
            "description": "We are having a picnic in the park and you are invited!",
            "start_at": "2020-03-01T12:00:00Z"
        },
        {
            "type": "Group",
            "id": "default-org-mariana-trench",
            "title": "Default Org: Mariana Trench",
            "description": "This group has the deepest conversations you will ever hear."
        },
        {
            "type": "ExternalEvent",
            "id": "https://example.com/sail-on-the-hudson"
            "title": "Sail on the Hudson",
            "description": "Go for a sail on the Hudson river to promote environmentalism.",
            "start_at": "2019-05-03T13:00:00Z"
        }
    ]
}

This JSON endpoint returns a paginated list of publicly listed events, groups, and external events in your organisation. It can be used alongside the /api/local/points endpoint; the criteria for inclusion are the same.

HTTP Request

GET https://demo.controlshiftlabs.com/api/local.json

Query Parameters

Parameter Default Description
page 1 (optional) Which page of results to fetch
per_page 10 (optional) How many results, maximum, should be included on each page

Logged in user

$(document).ready(function(){
  $.ajax({
    url: 'https://demo.controlshiftlabs.com/api/graph/me.json',
    dataType: 'jsonp',
  })
  .done(function(data) {
    console.log(data);
  });
});

The above code would user details for a signed in user. The returned JSON would be structured like this:

{
  "status":"authenticated",
  "me":{
    "first_name":"John",
    "last_name":"Smith",
    "email":"john@smith.com"
  },
  "petitions":[
    {
       "slug":"sample-campaign",
       "title":"Sample Campaign",
       "created_at":"2016-03-25T15:37:28Z",
       "updated_at":"2016-03-25T15:37:35Z"
    }
  ],
  "events":[
    {
       "slug":"sample-campaign",
       "title":"Sample Campaign",
       "created_at":"2016-03-25T15:37:28Z",
       "updated_at":"2016-03-25T15:37:35Z"
    }
  ],
  "chapters":[
    {
       "name":"Brooklyn bruisers",
       "slug":"brooklyn-bruisers",
       "created_at":"2015-05-14T15:53:03Z",
       "updated_at":"2016-03-25T15:40:44Z"
    }
  ]
}

Response for a visitor who is not logged in:

{
  "status":"not_authenticated",
  "message":"user is not signed in"
}

Response for a hostname that is not whitelisted:

{
  "status":"error",
  "errors":[
    {
      "field":"referer",
      "messages":[
        "host name not whitelisted"
      ]
    }
  ]
}

We provide a specialized jsonp endpoint to return information about the currently signed in user. Customers can use this endpoint to power features where information about the current user’s petitions, sign in status, and other information about their account is displayed on external sites. Some examples of the sorts of functionality that this could be used to provide include:

HTTP Request

GET http://demo.controlshiftlabs.com/api/graph/me.json

Working Example

View and edit a working example on codepen.io. Note: codepen.io is whitelisted for the demo account.

Whitelisting a Hostname

We authenticate requests to the current user endpoint by validating the hostname set in the HTTP referrer header against a whitelist. The whitelist is set of hostnames that scripts can be served from. If you request the endpoint from a script served from a non-whitelisted hostname you’ll get an error message. This is necessary to prevent cross site scripting attacks that would allow someone to display logged in user information on unauthorized sites.

You can add hostnames to the whitelist through a self-serve interface in the “Settings” area of your instance. Settings > Add Hostname > enter a Hostname, and click save.

The hostname must be a hostname string like “localhost” “www.google.com” or the like. Wildcards or full HTTP URLs are not supported.

When you access the graph API endpoint from a page served from a whitelisted hostname the browser will automatically set a referrer header on those requests. We use this referrer header to validate the server the script was served from against the whitelist. You can verify that a whitelisted hostname is working properly with the curl command line tool:

curl --referer http://localhost/ http://demo.controlshiftlabs.com/api/graph/me.json

The above should return a JSON blob indicating that the user is not signed in if localhost is whitelisted. Otherwise you’ll get a not whitelisted error.

For authenticated users we currently return their first and last names, email addresses, and lists of petitions they’ve created, events they’ve created and local groups they are a member of.

Bulk data schema

This retrieves information on the underlying schemas of tables exported by the Bulk Data feature. It can be used in your ETL processes to automate the setup of tables in your data warehouse.

Response format:

{
  "tables": [
    {
      "table": {
        "name": "petitions",
        "columns": [
          "title": {
            "type": "string",
            "sql_type": "character varying",
            "limit": null,
            "nullable": true,
            "default_value": null
          },
          "custom_goal": {
            "type": "integer",
            "sql_type": "integer",
            "limit": 4,
            "nullable": true,
            "default_value": null
          },
          ...
        ]
      }
    },
    ...
  ]
}

HTTP Request

GET https://demo.controlshiftlabs.com/bulk_data/schema.json

Bulk data columns

This retrieves information on the tables exported by the Bulk Data API. For convenience when working with third-party data warehouse tools like Amazon Redshift it can be useful to know the current columns for each table that the bulk data API exposes in CSV format.

The response is equivalent to the header row of tables exposed through our Bulk Data API. The table parameter controls which table header row is returned.

Response format:

id,petition_id,email,first_name,last_name,phone_number,postcode,created_at,join_organisation,deleted_at,unsubscribe_at,external_constituent_id,member_id,additional_fields,cached_organisation_slug,source,join_group,external_id,new_member,external_action_id,locale,obfuscated_bsd_cons_id,bucket,country,updated_at,user_ip,confirmation_token,confirmed_at,confirmation_sent_at,last_signed_at,join_list_suppressed,old_daisy_chain_used,bsd_ab_test_cons_group_id,from_embed,user_agent,confirmed_reason,synced_to_crm_at,daisy_chain_experiment_slug,eu_data_processing_consent,from_one_click,consent_content_version_id,daisy_chain_id_used,email_opt_in_type_id,facebook_id,utm_params,postcode_id,referring_share_click_id

HTTP Request

GET https://demo.controlshiftlabs.com/bulk_data/schema/columns?table=signatures

Query Parameters

Parameter Description
table Which table from the bulk data schema endpoint should be returned

Webhook Endpoints

Webhooks can be used by software engineers to integrate ControlShift with third-party systems. They allow engineers to build software that is triggered by events that take place within ControlShift. ControlShift would execute HTTP calls when certain event happen (e.g. a petition is launched; a category is changed).

To begin using these new webhooks, go to the admin homepage and click “Settings” under “Manage.” Then choose the “CRM Integration & Webhooks” tab and click “Configure Webhook Endpoints.” To begin sending data, you’ll need to add a “New Webhook Endpoint.” Each Webhook Endpoint URL receives a full firehose of all hooks that occur within ControlShift.

We recommend using a tool like RequestBin to help debug and develop your webhook integration. RequestBin allows engineers to see all of the webhooks generated by the application and sent to an endpoint.

If you need additional information about webhooks or how to use them, please send us a support email support@controlshiftlabs.com. We’d also love stories about how customers are using webhooks.

Retries

For webhook notifications successfully processed the response should have an HTTP 2XX status code. Responses returning a different status code will be marked as failed and automatically retried up to 9 times with an exponential backoff (e.g.: after 15s, 30s, 90s, etc.).

If after 9 retries the notification cannot be delivered, the webhook endpoint will be automatically disabled and will need to be re-enabled from the admin page to start receiving webhook notifications again.

IP Addresses

Below you can find a list of the IP addresses that webhook deliveries will originate from, in case you want to whitelist them:

Webhook types summary

You can configure ControlShift Labs Webhooks to return the following event types:

Type Description
blast_email.created A new blast email is ready for moderation
category.created A new category is created
category.updated A category is changed
category.deleted A category has been deleted and removed from the organisation
data.full_table_exported A full-table dump is ready for retrieval at S3
data.incremental_table_exported An incremental CSV update is ready for retrieval at S3
event.created A new event is published
event.updated An event is updated
attendee.created Member RSVP to event
attendee.confirmed Member confirmed RSVP and email address. This event is only emitted for organisations who have email confirmation turned on. This event is only emitted for explicit attendee confirmations, not implicit confirmations eg: for logged in users.
attendee.updated Member updates RSVP to event
attendee.deleted Attendee permanently deleted. This is currently only triggered when a member deletes its account.
flag.created Someone flags a problem for admin attention
label.applied A label is applied to a campaign, event, group, or member
label.removed A label is removed from a campaign, event, group, or member
local_chapter.created A local group has been created.
local_chapter.last_organiser.deleted A local chapter’s last organiser leaves the group
local_chapter.organiser_request.created A user applies to be a local chapter organiser
local_chapter.organiser_request.approved An admin approves a user’s application to be a local chapter organiser
local_chapter.organiser.created An organiser is added to a local chapter
local_chapter.member.created A member joins a local chapter
local_chapter.member.deleted A member leaves or is removed from a local chapter
member.deleted A member is deleted
member.deleted.resources_transferred Resources previously owned by a deleted member have been transferred to another
petition.ended A petition is marked as won, lost, or ended for another reason
petition.flagged A petition is flagged for the first or fifth time
petition.inappropriate.creator_message An inappropriate petition’s creator writes a message to admins
petition.launched A new petition is launched
petition.launched.ham A petition passes the post-launch spam check
petition.launched.requires_moderation A new petition requires moderation
petition.reactivated A hidden or ended petition is reactivated
petition.updated A petition is updated
petition.updated.requires_moderation An updated petition requires moderation
petition.target.response Decision maker responded to a petition notification
user.created New user created.
user.confirmed User has confirmed its new account. This event is only emitted for organisations who have user confirmation turned on.
user.changed A user has changed their saved first_name, last_name, postcode, locale or email opt-in type.
user.updated A user is updated.
signature.created Member signs a petition
signature.updated Existing signature updated. This can happen when member unsubscribes or reasserts their opt in for receiving emails
signature.deleted Signature permanently deleted. Signature deletions are distinct from unsubscribes and should be treated as if the signature never happened
signature.confirmed
unsubscribe.created Member unsubscribes. This notification is emitted when member: is globally unsubscribed from the organisation by an admin or through the API, unsubscribes from partnership or unsubscribes from a petition.
locale.created New i18n instance created
forum.message.requires_moderation A new message requires moderation

blast_email.created

Example payload for blast_email.created:

  {
  "type": "blast_email.created",
  "data": {
    "id": 100,
    "from_name": "Jane Doe",
    "from_address": "jane@example.com",
    "subject": "Please help spread the word",
    "recipient_count": 990
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A new blast email is ready for moderation

category.created

Example payload for category.created:

  {
  "type": "category.created",
  "data": {
    "id": 22,
    "name": "Environment",
    "slug": "environment",
    "external_id": null,
    "created_at": "2015-08-03T12:38:46Z",
    "updated_at": "2015-08-03T12:38:46Z"
  },
  "jid": "df67126973794c2efde76cc4"
}

A new category is created

category.updated

Example payload for category.updated:

  {
  "type": "category.updated",
  "data": {
    "id": 22,
    "name": "Environment",
    "slug": "environment",
    "external_id": null,
    "created_at": "2015-08-03T12:38:46Z",
    "updated_at": "2015-08-03T12:38:46Z"
  },
  "jid": "df67126973794c2efde76cc4"
}

A category is changed

category.deleted

Example payload for category.deleted:

  {
  "type": "category.deleted",
  "data": {
    "id": 22,
    "name": "Environment",
    "slug": "environment",
    "external_id": null,
    "created_at": "2015-08-03T12:38:46Z",
    "updated_at": "2015-08-03T12:38:46Z"
  },
  "jid": "df67126973794c2efde76cc4"
}

A category has been deleted and removed from the organisation

data.full_table_exported

Example payload for data.full_table_exported:

  {
  "type": "data.full_table_exported",
  "data": {
    "url": "https://agra-data-exports.s3.amazonaws.com/default/petitions_20150803180434.csv?AWSAccessKeyId=XXX&Expires=1438711490&Signature=ABCDE%3D",
    "table": "petitions"
  },
  "jid": "cf0f50d7d225e20a188be328"
}

A full-table dump is ready for retrieval at S3

Note URL is encoded using (7-bit) ASCII and some characters will be escaped (i.e.: ‘&’, ‘<’, ‘>’, etc.). See http://www.rubydoc.info/gems/activesupport/3.2.22/ActiveSupport/JSON/Encoding for more details

data.incremental_table_exported

Example payload for data.incremental_table_exported:

  {
  "type": "data.incremental_table_exported",
  "data": {
    "url": "https://agra-data-exports.s3.amazonaws.com/default/petitions_20150803180434.csv?AWSAccessKeyId=XXX&Expires=1438711490&Signature=ABCDE%3D",
    "table": "petitions"
  },
  "jid": "cf0f50d7d225e20a188be328"
}

An incremental CSV update is ready for retrieval at S3

Note URL is encoded using (7-bit) ASCII and some characters will be escaped (i.e.: ‘&’, ‘<’, ‘>’, etc.). See http://www.rubydoc.info/gems/activesupport/3.2.22/ActiveSupport/JSON/Encoding for more details

event.created

Example payload for event.created:

  {
  "type": "event.created",
  "data": {
    "slug": "petition-hand-in",
    "title": "Petition Hand In",
    "url": "https://demo.controlshiftlabs.com/events/petition-hand-in"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A new event is published

event.updated

Example payload for event.updated:

  {
  "type": "event.updated",
  "data": {
    "slug": "petition-hand-in",
    "title": "Petition Hand In",
    "url": "https://demo.controlshiftlabs.com/events/petition-hand-in"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

An event is updated

attendee.created

Example payload for attendee.created:

  {
  "type": "attendee.created",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "attending_status": "attending",
    "join_organisation": null,
    "join_group": null,
    "user_ip": "127.0.0.1",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "event": {
      "url": "http://localhost/events/meetup-at-park",
      "slug": "meetup-at-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "source": "facebook",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

Member RSVP to event

attendee.confirmed

Example payload for attendee.confirmed:

  {
  "type": "attendee.confirmed",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "attending_status": "attending",
    "join_organisation": null,
    "join_group": null,
    "user_ip": "127.0.0.1",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "event": {
      "url": "http://localhost/events/meetup-at-park",
      "slug": "meetup-at-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "source": "facebook",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

Member confirmed RSVP and email address. This event is only emitted for organisations who have email confirmation turned on. This event is only emitted for explicit attendee confirmations, not implicit confirmations eg: for logged in users.

attendee.updated

Example payload for attendee.updated:

  {
  "type": "attendee.updated",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "attending_status": "attending",
    "join_organisation": null,
    "join_group": null,
    "user_ip": "127.0.0.1",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "event": {
      "url": "http://localhost/events/meetup-at-park",
      "slug": "meetup-at-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "source": "facebook",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

Member updates RSVP to event

attendee.deleted

Example payload for attendee.deleted:

  {
  "type": "attendee.deleted",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "attending_status": "attending",
    "join_organisation": null,
    "join_group": null,
    "user_ip": "127.0.0.1",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "event": {
      "url": "http://localhost/events/meetup-at-park",
      "slug": "meetup-at-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "source": "facebook",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

Attendee permanently deleted. This is currently only triggered when a member deletes its account.

flag.created

Example payload for flag.created:

  {
  "type": "flag.created",
  "data": {
    "id": 7,
    "flagged": {
      "type": "ForumMessage",
      "id": 224
    },
    "flagged_by": {
      "email": "bobby@example.com",
      "name": "Bobby Singer"
    },
    "reason": "This violates our community commitments",
    "status": "unreviewed",
    "created_at": "2015-08-05T13:17:46Z",
    "updated_at": "2015-08-05T13:17:46Z"
  },
  "jid": "f848f4b38b9125f7089d59ad"
}

Someone flags a problem for admin attention

For example, a local chapter organiser flags a forum message.

label.applied

Example payload for label.applied:

A label is applied to a campaign, event, group, or member

label.removed

Example payload for label.removed:

A label is removed from a campaign, event, group, or member

local_chapter.created

Example payload for local_chapter.created:

  {
  "type": "local_chapter.created",
  "data": {
    "slug": "springfield-members",
    "name": "Springfield Members",
    "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A local group has been created.

local_chapter.last_organiser.deleted

Example payload for local_chapter.last_organiser.deleted:

  {
  "type": "local_chapter.last_organiser.deleted",
  "data": {
    "slug": "springfield-members",
    "name": "Springfield Members",
    "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A local chapter’s last organiser leaves the group

local_chapter.organiser_request.created

Example payload for local_chapter.organiser_request.created:

  {
  "type": "local_chapter.organiser_request.created",
  "data": {
    "id": 1234,
    "local_chapter": {
      "slug": "springfield-members",
      "name": "Springfield Members",
      "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
    },
    "user": {
      "id": 505,
      "email": "jane@example.com",
      "full_name": "Jane Goodall",
      "first_name": "Jane",
      "last_name": "Goodall",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "reason": "I want to be part of this amazing group",
    "created_at": "2015-08-03T12:38:46Z"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A user applies to be a local chapter organiser

local_chapter.organiser_request.approved

Example payload for local_chapter.organiser_request.approved:

  {
  "type": "local_chapter.organiser_request.approved",
  "data": {
    "id": 1234,
    "local_chapter": {
      "slug": "springfield-members",
      "name": "Springfield Members",
      "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
    },
    "user": {
      "id": 505,
      "email": "jane@example.com",
      "full_name": "Jane Goodall",
      "first_name": "Jane",
      "last_name": "Goodall",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "reason": "I want to be part of this amazing group",
    "created_at": "2015-08-03T12:38:46Z"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

An admin approves a user’s application to be a local chapter organiser

local_chapter.organiser.created

Example payload for local_chapter.organiser.created:

  {
  "type": "local_chapter.organiser.created",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": null,
    "postcode": "23456",
    "country": "US",
    "introduction": "I want to save the world!",
    "notification_level": "all_messages",
    "local_chapter": {
      "slug": "springfield-members",
      "name": "Springfield Members",
      "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

An organiser is added to a local chapter

local_chapter.member.created

Example payload for local_chapter.member.created:

  {
  "type": "local_chapter.member.created",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": null,
    "postcode": "23456",
    "country": "US",
    "introduction": "I want to save the world!",
    "notification_level": "all_messages",
    "local_chapter": {
      "slug": "springfield-members",
      "name": "Springfield Members",
      "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A member joins a local chapter

local_chapter.member.deleted

Example payload for local_chapter.member.deleted:

  {
  "type": "local_chapter.member.deleted",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": null,
    "postcode": "23456",
    "country": "US",
    "introduction": "I want to save the world!",
    "notification_level": "all_messages",
    "local_chapter": {
      "slug": "springfield-members",
      "name": "Springfield Members",
      "url": "https://demo.controlshiftlabs.com/local_chapters/springfield-members"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks"
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A member leaves or is removed from a local chapter

member.deleted

Example payload for member.deleted:

  {
  "type": "member.deleted",
  "data": {
    "member": {
      "id": 100,
      "email": "jane@example.com"
    },
    "resources": {
      "new_owner": {
        "id": 888,
        "email": "richard@example.com",
        "full_name": "Richard Chamberlain",
        "first_name": "Richard",
        "last_name": "Chamberlain",
        "phone_number": "12345-1",
        "postcode": "34058-2356"
      }
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A member is deleted

member.deleted.resources_transferred

Example payload for member.deleted.resources_transferred:

  {
  "type": "member.deleted.resources_transferred",
  "data": {
    "member": {
      "id": 100,
      "email": "jane@example.com"
    },
    "resources": {
      "new_owner": {
        "id": 888,
        "email": "richard@example.com",
        "full_name": "Richard Chamberlain",
        "first_name": "Richard",
        "last_name": "Chamberlain",
        "phone_number": "12345-1",
        "postcode": "34058-2356"
      }
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

Resources previously owned by a deleted member have been transferred to another

petition.ended

Example payload for petition.ended:

  {
  "type": "petition.ended",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A petition is marked as won, lost, or ended for another reason

petition.flagged

Example payload for petition.flagged:

  {
  "type": "petition.flagged",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A petition is flagged for the first or fifth time

petition.inappropriate.creator_message

Example payload for petition.inappropriate.creator_message:

  {
  "type": "petition.inappropriate.creator_message",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

An inappropriate petition’s creator writes a message to admins

petition.launched

Example payload for petition.launched:

  {
  "type": "petition.launched",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A new petition is launched

petition.launched.ham

Example payload for petition.launched.ham:

  {
  "type": "petition.launched.ham",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A petition passes the post-launch spam check

petition.launched.requires_moderation

Example payload for petition.launched.requires_moderation:

  {
  "type": "petition.launched.requires_moderation",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A new petition requires moderation

petition.reactivated

Example payload for petition.reactivated:

  {
  "type": "petition.reactivated",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A hidden or ended petition is reactivated

petition.updated

Example payload for petition.updated:

  {
  "type": "petition.updated",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

A petition is updated

petition.updated.requires_moderation

Example payload for petition.updated.requires_moderation:

  {
  "type": "petition.updated.requires_moderation",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

An updated petition requires moderation

petition.target.response

Example payload for petition.target.response:

  {
  "type": "petition.target.response",
  "data": {
    "slug": "don-t-destroy-our-park",
    "title": "Don't destroy our park!",
    "url": "https://demo.controlshiftlabs.com/petitions/don-t-destroy-our-park",
    "image_url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/hero/imag0945.jpg?1438621934",
    "additional_image_sizes_url": [
      {
        "style": "form",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/form/imag0945.jpg?1438621934"
      },
      {
        "style": "large",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/large/imag0945.jpg?1438621934"
      },
      {
        "style": "open_graph",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/open_graph/imag0945.jpg?1438621934"
      },
      {
        "style": "original",
        "url": "https://d34smfggpfnvat.cloudfront.net/petitions/images/788/original/imag0945.jpg?1438621934"
      }
    ],
    "who": "Anytown City Council",
    "what": "The park on Main Street is an important community space.  Don't approve the proposal to sell it to office building developers.",
    "goal": 100,
    "signature_count": 0,
    "creator": {
      "id": 1,
      "email": "agra.user.adm@gmail.com",
      "full_name": "Tyshawn Morissette",
      "first_name": "Tyshawn",
      "last_name": "Morissette",
      "phone_number": "12345-1",
      "postcode": "34058-2356"
    },
    "locale": "en-US",
    "created_at": "2015-08-03T13:11:02Z",
    "updated_at": "2015-08-03T13:12:15Z",
    "why": "Studies have shown that green space is important to child development and community well-being.",
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We won the campaign! The Main Street park will be protected for years to come.",
    "successful": true,
    "ended_story": "We won! Thanks to your support, the city council has agreed to protect the Main Street park from development."
  },
  "jid": "9fccc1957d7d7ebc93fb0f05"
}

Decision maker responded to a petition notification

user.created

Example payload for user.created:

  {
  "type": "user.created",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": "123-456",
    "postcode": "101010",
    "country": "US",
    "confirmed_at": "2015-08-03T13:30:01Z",
    "join_organisation": true,
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

New user created.

user.confirmed

Example payload for user.confirmed:

  {
  "type": "user.confirmed",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": "123-456",
    "postcode": "101010",
    "country": "US",
    "confirmed_at": "2015-08-03T13:30:01Z",
    "join_organisation": true,
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

User has confirmed its new account. This event is only emitted for organisations who have user confirmation turned on.

user.changed

Example payload for user.changed:

  {
  "type": "user.changed",
  "data": {
    "id": 180,
    "email": "foo@bar.com",
    "changes": {
      "first_name": [
        "Nate",
        "Nathan"
      ],
      "email_opt_in_type": [
        {
          "context": "web_form",
          "kind": "pre_checked_checkbox",
          "mailable": true,
          "external_id": "",
          "active": true
        },
        {
          "context": "web_form",
          "kind": "unchecked_checkbox",
          "mailable": true,
          "external_id": "",
          "active": true
        }
      ]
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A user has changed their saved first_name, last_name, postcode, locale or email opt-in type.

user.updated

Example payload for user.updated:

  {
  "type": "user.updated",
  "data": {
    "id": 180,
    "email": "jenny@example.com",
    "full_name": "Jennifer Goines",
    "first_name": "Jennifer",
    "last_name": "Goines",
    "phone_number": "123-456",
    "postcode": "101010",
    "country": "US",
    "confirmed_at": "2015-08-03T13:30:01Z",
    "join_organisation": true,
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345"
    },
    "changes": {
      "first_name": [
        "Nate",
        "Nathan"
      ],
      "email_opt_in_type": [
        {
          "context": "web_form",
          "kind": "pre_checked_checkbox",
          "mailable": true,
          "external_id": "",
          "active": true
        },
        {
          "context": "web_form",
          "kind": "unchecked_checkbox",
          "mailable": true,
          "external_id": "",
          "active": true
        }
      ]
    }
  },
  "jid": "030ee43a4fa9788e084d9cfc"
}

A user is updated.

signature.created

Example payload for signature.created:

  {
  "type": "signature.created",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "country_without_fallback": "",
    "join_organisation": null,
    "join_group": null,
    "locale": "en",
    "token": "2919ec5f25d493c19690a21135d7fd902a336927",
    "source": "facebook",
    "bucket": "share-campaign-v2",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks",
    "user_ip": "127.0.0.1",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.130 Chrome/43.0.2357.130 Safari/537.36",
    "from_embed": null,
    "confirmed_at": null,
    "confirmed_reason": null,
    "last_signed_at": "2015-08-03T13:30:01Z",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "petition": {
      "url": "http://localhost/petitions/don-t-destroy-our-park",
      "slug": "don-t-destroy-our-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "updated_at": "2015-08-03T13:30:01Z",
    "deleted_at": null,
    "unsubscribe_at": null
  },
  "jid": "1ebf2eb10c3d8939f42dff68"
}

Member signs a petition

We also include optional partnership or effort slugs nested under the petition resource for signatures that take place on petitions associated with those objects.

Field Help
join_organisation If the user opts in to receive email from the organisation, this is true. If the user does not wish to receive email or join the organisation this is false.
confirmed_at A timestamp, set when the user confirms their email address and signature. Used for double opt in.
confirmed_reason The mechanism through which the user confirmed their signature
email_opt_in_type A representation of the mechanism that the member used while opting in or out of email.

signature.updated

Example payload for signature.updated:

  {
  "type": "signature.updated",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "country_without_fallback": "",
    "join_organisation": null,
    "join_group": null,
    "locale": "en",
    "token": "2919ec5f25d493c19690a21135d7fd902a336927",
    "source": "facebook",
    "bucket": "share-campaign-v2",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks",
    "user_ip": "127.0.0.1",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.130 Chrome/43.0.2357.130 Safari/537.36",
    "from_embed": null,
    "confirmed_at": null,
    "confirmed_reason": null,
    "last_signed_at": "2015-08-03T13:30:01Z",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "petition": {
      "url": "http://localhost/petitions/don-t-destroy-our-park",
      "slug": "don-t-destroy-our-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "updated_at": "2015-08-03T13:30:01Z",
    "deleted_at": null,
    "unsubscribe_at": null
  },
  "jid": "1ebf2eb10c3d8939f42dff68"
}

Existing signature updated. This can happen when member unsubscribes or reasserts their opt in for receiving emails

We also include optional partnership or effort slugs nested under the petition resource for signatures that take place on petitions associated with those objects.

Field Help
join_organisation If the user opts in to receive email from the organisation, this is true. If the user does not wish to receive email or join the organisation this is false.
confirmed_at A timestamp, set when the user confirms their email address and signature. Used for double opt in.
confirmed_reason The mechanism through which the user confirmed their signature
email_opt_in_type A representation of the mechanism that the member used while opting in or out of email.

signature.deleted

Example payload for signature.deleted:

  {
  "type": "signature.deleted",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "country_without_fallback": "",
    "join_organisation": null,
    "join_group": null,
    "locale": "en",
    "token": "2919ec5f25d493c19690a21135d7fd902a336927",
    "source": "facebook",
    "bucket": "share-campaign-v2",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks",
    "user_ip": "127.0.0.1",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.130 Chrome/43.0.2357.130 Safari/537.36",
    "from_embed": null,
    "confirmed_at": null,
    "confirmed_reason": null,
    "last_signed_at": "2015-08-03T13:30:01Z",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "petition": {
      "url": "http://localhost/petitions/don-t-destroy-our-park",
      "slug": "don-t-destroy-our-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "updated_at": "2015-08-03T13:30:01Z",
    "deleted_at": null,
    "unsubscribe_at": null
  },
  "jid": "1ebf2eb10c3d8939f42dff68"
}

Signature permanently deleted. Signature deletions are distinct from unsubscribes and should be treated as if the signature never happened

We also include optional partnership or effort slugs nested under the petition resource for signatures that take place on petitions associated with those objects.

Field Help
join_organisation If the user opts in to receive email from the organisation, this is true. If the user does not wish to receive email or join the organisation this is false.
confirmed_at A timestamp, set when the user confirms their email address and signature. Used for double opt in.
confirmed_reason The mechanism through which the user confirmed their signature
email_opt_in_type A representation of the mechanism that the member used while opting in or out of email.

signature.confirmed

Example payload for signature.confirmed:

  {
  "type": "signature.confirmed",
  "data": {
    "id": 645,
    "first_name": "Jennifer",
    "last_name": "Goines",
    "email": "jenny@example.com",
    "phone_number": null,
    "postcode": "23456",
    "country": "",
    "country_without_fallback": "",
    "join_organisation": null,
    "join_group": null,
    "locale": "en",
    "token": "2919ec5f25d493c19690a21135d7fd902a336927",
    "source": "facebook",
    "bucket": "share-campaign-v2",
    "utm_source": "facebook",
    "utm_campaign": "share-campaign-v2",
    "utm_medium": "social",
    "utm_content": "share-variant-3",
    "utm_term": "parks",
    "user_ip": "127.0.0.1",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/43.0.2357.130 Chrome/43.0.2357.130 Safari/537.36",
    "from_embed": null,
    "confirmed_at": null,
    "confirmed_reason": null,
    "last_signed_at": "2015-08-03T13:30:01Z",
    "additional_fields": {
    },
    "email_opt_in_type": {
      "context": "web_form",
      "kind": "pre_checked_checkbox",
      "mailable": true,
      "external_id": "abcd12345"
    },
    "eu_data_processing_consent": true,
    "consent_content_version": {
      "id": 123,
      "external_id": "abc345",
      "consent_type": "explicit"
    },
    "member": {
      "id": 747,
      "created_at": "2015-08-03T13:30:01Z"
    },
    "petition": {
      "url": "http://localhost/petitions/don-t-destroy-our-park",
      "slug": "don-t-destroy-our-park"
    },
    "created_at": "2015-08-03T13:30:01Z",
    "updated_at": "2015-08-03T13:30:01Z",
    "deleted_at": null,
    "unsubscribe_at": null
  },
  "jid": "1ebf2eb10c3d8939f42dff68"
}

We also include optional partnership or effort slugs nested under the petition resource for signatures that take place on petitions associated with those objects.

Field Help
join_organisation If the user opts in to receive email from the organisation, this is true. If the user does not wish to receive email or join the organisation this is false.
confirmed_at A timestamp, set when the user confirms their email address and signature. Used for double opt in.
confirmed_reason The mechanism through which the user confirmed their signature
email_opt_in_type A representation of the mechanism that the member used while opting in or out of email.

unsubscribe.created

Example payload for unsubscribe.created:

  [
  {
    "type": "unsubscribe.created",
    "data": {
      "id": 3,
      "email": "river@example.com",
      "unsubscribe_organisation": true,
      "unsubscribable_type": null,
      "created_at": "2015-08-03T13:38:49Z"
    },
    "jid": "030ee43a4fa9788e084d9cfc"
  },
  {
    "type": "unsubscribe.created",
    "data": {
      "id": 3,
      "email": "river@example.com",
      "unsubscribe_organisation": false,
      "unsubscribable_type": "PartnershipSubscription",
      "created_at": "2015-08-03T13:38:49Z"
    },
    "jid": "030ee43a4fa9788e084d9cfc"
  },
  {
    "type": "unsubscribe.created",
    "data": {
      "id": 3,
      "email": "river@example.com",
      "unsubscribe_organisation": true,
      "unsubscribable_type": "Signature",
      "created_at": "2015-08-03T13:38:49Z",
      "blast_email": {
        "id": 199,
        "subject": "Please help spread the word"
      }
    },
    "jid": "030ee43a4fa9788e084d9cfc"
  }
]

Member unsubscribes. This notification is emitted when member: is globally unsubscribed from the organisation by an admin or through the API, unsubscribes from partnership or unsubscribes from a petition.

First example: Admin globally unsubscribes a member from the member page

Second example: Member unsubscribes from partnership
Note: unsubscribe_organisation can have true or false values depending on member’s input at the unsubscribe page (only if the Allow global unsubscribes option is enabled for the organisation)

Third example: Member unsubscribes from petition
Notes:
unsubscribe_organisation can have true or false values depending on member’s input at the unsubscribe page (only if the Allow global unsubscribes option is enabled for the organisation)
The blast_email property will only be included if member unsubscribed by clicking on the link included on the petition blast email’s footer

locale.created

Example payload for locale.created:

  {
  "type": "locale.created",
  "data": {
    "id": 22,
    "name": "es",
    "action_kit_external_id": null,
    "created_at": "2015-08-03T12:38:46Z",
    "updated_at": "2015-08-03T12:38:46Z"
  },
  "jid": "df67126973794c2efde76cc4"
}

New i18n instance created

forum.message.requires_moderation

Example payload for forum.message.requires_moderation:

  {
  "type": "forum.message.requires_moderation",
  "data": {
    "id": 10,
    "thread_title": "Hello everyone",
    "content": "I just wanted to say hi",
    "admin_status": "unreviewed",
    "sent_at": "2015-08-05T13:17:46Z",
    "member": {
      "id": 10829,
      "created_at": "2015-08-05T13:17:46Z"
    },
    "discussable": {
      "discussable_type": "LocalChapter",
      "discussable_id": 56
    }
  },
  "jid": "f848f4b38b9125f7089d59ad"
}

A new message requires moderation

Note
The admin_status field can take any of the following values:
  • unreviewed
  • flagged
  • approved_automatically
  • approved_manually
  • flagged_deleted
  • inappropriate
Though on the forum.message.requires_moderation notification type it will always be set to unreviewed
The discussable.discussable_type field can take any of the following values:
  • LocalChapter
  • Event
  • LocalChapterEvent
  • PetitionEvent

Bulk Data

ControlShift’s Bulk Data Webhooks make it easy to pull your data into external services.

Webhooks

We provides two special bulk data webhooks to help you keep an external reporting or analytics database server up to date with information from ControlShift’s internal tables. The data.full_table_exported and data.incremental_table_exported webhooks can be consumed to keep an external database mirror containing ControlShift data up to date. This service was built in a database agnostic way, but it should be possible to build a ControlShift -> Amazon Redshift data pipeline using the ControlShift to Redshift Pipeline technique outlines below.

Bulk Data Data Schemas

The bulk data webhooks include exports of the following tables:

For full information on the schema of each table, use the /api/bulk_data/schema.json API endpoint.

ControlShift to Redshift Pipeline

Setting up an Amazon Redshift integration us a great way to learn more about the actions your members are taking or perform sophisticated analytics but is an advanced topic that requires knowledge of Amazon Web Services, SQL, and Terraform.

Following these instructions we’ll use a custom AWS Lambda to receive data from ControlShift’s bulk data API webhooks and Amazon’s Lambda Redshift Loader to load our data into Redshift. The final result will be a replica of the tables that back your instance of ControlShift that are refreshed in full each night and appended with inserts on a per-minute basis.

ControlShift has authored a Terraform module that makes the entire setup process straightforward and allows for updates to the infrastructure over time.

Example Flow

ControlShift Redshift Flow Chart

Redshift Pipeline Setup

If you are not yet using Redshift or Terraform, we’ve written an example Terraform plan that should create a working integration, a new Redshift instance and appropriate permissions from an otherwise empty AWS Account.

If you are already using Terraform or Redshift it is probably best to either fork this example or use the module we provide directly in your own TF plan

Applying the Terraform plan in your AWS account will create all of the resources necessary and output the Webhook URL that you’ll need to configure within your ControlShift platform settings.

Prepare Redshift Schema

We need to prep tables in your Redshift Data Warehouse to receive our ControlShift Data. We’ll use psql for this.

  1. Install psql if you don’t have it already.
  2. Download the current SQL DDL for ControlShift’s tables tables.sql or generate an up to date one using this script. that uses our Schema API.
  3. Finally, you’ll need to load that set of tables into your Redshift instance with the following command:

psql -h <cluster_endpoint> -U <database_user> -d <database_name> -p <cluster_port> -f petitions_schema.sql

Configure ControlShift’s Webhook

Finally, you’ll need to log into your admin panel. Settings > Integrations > Webhooks. Then add the bulk data webhook pointing at your Lambda endpoint output by the Terraform plan.

More information.

More information is available on the READMEs for the various projects that this integration is composed from. We can also provide assistance for customers configuring these tools via support@controlshiftlabs.com

Authenticated REST API

The Authenticated REST API allows customers to build applications that interact with their own data or securely grant access to third-party app developers without exposing administrative credentials. It is designed to be consumed server-side, in contrast to the JSONP API which is designed for unauthenticated javascript integrations.

We use OAuth2 to allow organizations to securely grant access to API Applications. OAuth2 is a standardized protocol that allows user credentials to be exchanged for a secret token which can then be used to access an API. There are OAuth2 implementations available for most common programming languages. You’ll probably want to read some general documentation on how OAuth2 works before attempting to use this API.

An API Application must be configured within your ControlShift instance before using the API. Access is then granted to this application, and exchanged for a token which is used to authenticate your access to API endpoints.

Rate Limits

We apply the following per-domain rate limits on HTTP requests made to the API:

Once the limit is reached, we will return an HTTP response with status code 429 - Too Many Requests and the following headers:

Quickstart Guide

  1. Set up a new API Application Log in as an Organisation Owner and navigate to Settings > Integrations > REST API Apps. Add a New Application. Use urn:ietf:wg:oauth:2.0:oob as the callback URL unless you plan to allow self-service application authorization.

  2. Clone the oauth-api-example github repo.

  3. Follow the instructions in the README.md file to get that example working with your credentials.

  4. Do something similar in your actual code.

Note: Access tokens have a 2 hours expiration, after that period you can use the old token to generate a new one.

Organisation

Retrieve

Get up to date statistics about the current organisation.

GET /api/v1/organisation

{
  "organisation": {
    "slug": "foo",
    "petitions_count": 3,
    "signatures_count": 4,
    "blast_emails_count": 0,
    "members_count": 5
  }
}

Email Opt In Types

List

Get a list of the email opt in types that have been configured for this organisation.

Email opt in types represent the way that members have opted in to your email list.

Context

The context this opt in type was displayed to users in.

Value Description
web_form an action on the web
email in a confirmation email
offline collected offline via a paper form
external represents opt ins gathered in other systems

Kind

The sort of opt in that was gathered

Value Description
email_confirmation an action on the web
implicit a message displayed while taking action
offline collected offline via a paper form
unchecked_checkbox a checkbox that began unchecked
physical_signature a wet ink signature
pre_checked_checkbox a checkbox that began checked
radio a radio button with yes/no options

Active

This parameter is true if the opt in type is currently being used in it’s specified context.

Mailable

Whether this opt-in type should be considered valid for email.

external_id

External IDs are set by customers through the admin interface while configuring opt in types. You can use them to match up email opt in types with your external systems, or to refer to email opt in types consistently through this API.

GET /api/v1/organisation/email_opt_in_types

{
  "email_opt_in_types": [{
    "context": "web_form",
    "kind": "implicit",
    "mailable": true,
    "active": true,
    "external_id": "your-external-id"
  }]
}

List

Get a list of the Data Processing Consent Content Versions that have been configured for this organisation.

These content versions track changes to the Terms of Service, Privacy Policy and Data Processing Consent disclaimers.

GET /api/v1/organisation/consent_content_versions

Members

The platform transparently creates a member record for any email address that creates a user account, signs a petition, or attends an event. Members are unique by email address, and all platform activity is tracked by member id.

GET response body for Lookup or Show

{
  "member": {
    "id": 123,
    "email": "foo@bar.com",
    "created_at": "2015-06-01T15:37:47Z",
    "updated_at": "2017-07-01T11:23:45Z",
    "external_id": "abc123",

    "data_processing_consent": {
      "consented_to_latest_version": false,

      "most_recent_consent_from_member": {
        "consented_at": "2018-01-01T04:00Z",

        "consent_content_version": {
          "id": "25",
          "external_id": "def456",
          "created_at": "2017-12-31T23:59Z"
        }
      }
    }
  }
}

Lookup

Find a member by email address. Once you have obtained a member id you can use this identifier in other API calls.

GET /api/v1/members/lookup?email=foo@bar.com

Show

Find a member by id.

The data_processing_consent block is only present when the data processing consent feature is enabled.

GET /api/v1/members/123

Activity

GET response body for Activity

{
  "first_name": "Jane",
  "last_name": "Doe",
  "phone_number": "555-555-5555",
  "created_at": "2015-06-01T15:37:47Z",

  "signatures": [
    {
      "email": "jane.doe@example.com",
      "petition": {
        "slug": "save-the-whales"
      }
      ...
    }
    ...
  ]
  ...
}

Get information about a member’s activity on the platform. This can be useful if you’re building a member dashboard that includes information from ControlShift and other toolsets.

The member activity report includes basic biographical information as well as petitions created or signed, events hosted or attended, group memberships, forum posts, partnership subscriptions, and unsubscribe history.

GET /api/v1/members/123/activity

Destroy

DELETE response body for Destroy

{
  "member": {
    "deleted": true,
    "id": 123,
    "email": "foo@bar.com"
  }
}

Permanently deletes a member with specified id and all associated records including petitions, signatures, events. The deletion feature is designed to honor requests from members around the right to be forgotten.

DELETE /api/v1/members/123

Deletes are synchronous and may take several seconds depending on how many resources are owned by the member.

Ownership of Petitions and Events created by the deleted member will be re-assigned to the user account specified in the organisation’s settings.

Anonymize

POST response body for Anonymize

{
  "member": {
    "deleted": true,
    "id": 123,
    "email": "foo@bar.com"
  }
}

Anonymizes the member with specified id. This permanently deletes the member and all associated signatures. However, petition signatures from the anonymized member are preserved, anonymized, on petition data exports, signature counts, and petition letters. The method of anonymization is configured in organisation name privacy settings.

POST /api/v1/members/123/anonymize

Anonymization is synchronous and may take several seconds depending on how many resources are owned by the member.

Ownership of Petitions and Events created by the deleted member will be re-assigned to the user account specified in the organisation’s settings.

Unsubscribe

POST response body for Unsubscribe

{
  "member": {
    "unsubscribed": true,
    "id": 123,
    "email": "foo@bar.com"
  }
}

Unsubscribes a member with specified id from emails from event hosts and petiton creators. Member will still receive transactional emails, and will be resubscribed if they opt in while signing a new petition or attending a new event, etc.

POST /api/v1/members/123/unsubscribe

Update Email Opt In Type

POST body

external_id=foo

Changes the email opt in type for the member with the specified id to the external id in the POST body. This will update the opt in type for all associated signatures, attendee records, user accounts and other data held by ControlShift to the opt in type specified by external_id.

This feature may be useful for upgrading the recorded consent when new consent for email communications has been collected in external systems.

Most likely you will be changing historical consents that may no longer be mailable for legal reasons to a new mailable consent type.

No history is stored recording the original consent type, it is replaced by the new one specified.

POST /api/v1/members/123/update_email_opt_in_type

Petitions

Petition are pieces of content that can be signed by members.

Show

GET response Body

{
  "petition": {
    "id": 123,
    "slug": "no-taxes-on-tea",
    "title": "No Taxes on Tea",
    "who": "King George",
    "what": "Stop taxing our tea",
    "why": "It is unjust.",
    "locale": "en",
    "admin_status": "good",
    "created_at": "2018-01-22T19:01:44Z",
    "updated_at": "2018-03-05T15:02:02Z",
    "signature_count_add_amount": 100
    "source": "effort_near",
    "admin_notes": [
      {
        "source": "legacy",
        "body": "An older note about this campaign",
        "created_at": "2018-01-25T23:45:12Z",
        "user": null
      },
      {
        "source": "user",
        "body": "We should have a call with this campaigner but they have no telephone.",
        "created_at": "2019-10-09T12:34:45Z",
        "user": {
          "email": "organiser_2@example.com",
          "full_name": "Sarah Organiser"
        }
      }
    ],
    "url": "https://demo.controlshiftlabs.com/petitions/no-taxes-on-tea",
    "public_who": "King George",
    "public_signature_count": 234,
    "goal": 500,
    "ended": true,
    "ended_type": "won",
    "ended_reason": "We overthrew the government and they don't tax our tea anymore.",
    "successful": true,
    "ended_story": "This was an overwhelming success!",
    "image": {
      "urls": {
        "open_graph": "http://cdn.example.com/petitions/images/25/open_graph/a-little-teapot.png?1516647704",
        "horizontal": "http://cdn.example.com/petitions/images/25/horizontal/a-little-teapot.png?1516647704",
        "hero": "http://cdn.example.com/petitions/images/25/hero/a-little-teapot.png?1516647704",
        "original": "http://cdn.example.com/petitions/images/25/original/a-little-teapot.png?1516647704",
        "form": "http://cdn.example.com/petitions/images/25/form/a-little-teapot.png?1516647704"
      },
      "description": "Picture of a small teapot with flowers on it and money tucked under the lid"
    },
    "creator": {
      "full_name": "Patrick Henry",
      "first_name": "Patrick",
      "last_name": "Henry",
      "email": "patrick.henry@example.com",
      "phone_number": "555-555-5555"
    },
    "location": {
      "query": "boston harbor",
      "latitude": "42.3376368",
      "longitude": "-70.99304",
      "venue": "Boston Harbor",
      "street_number": "",
      "street": "",
      "region": "MA",
      "postal_code": "",
      "country": "US",
      "created_at": "2017-11-16T22:38:29Z"
    },
    "decision_makers": [
      {
        "slug": "king-george",
        "name": "George",
        "context": "King of England",
        "phone_number": "",
        "email": "",
        "location": {
          "query": "buckingham palace",
          "latitude": "51.501364",
          "longitude": "-0.1440787",
          "venue": "Buckingham Palace",
          "street_number": "",
          "street": "",
          "region": "London",
          "postal_code": "SW1A 1AA",
          "country": "UK",
          "created_at": "2017-11-16T22:38:29Z"
        }
      }
    ],
    "effort": {
      "slug": "no-taxation-without-representation"
    },
    "partnership": {
      "slug": "the-american-revolution"
    }
  }
}

Find information about a petition by URL slug.

GET /api/v1/petitions/no-taxes-on-tea

List

GET response body

{
  "petitions": [
    {
      "id": 123,
      "slug": "no-taxes-on-tea",
      "title": "No Taxes on Tea",
      ...
    },
    {
      "id": 124,
      "slug": "stop-burning-coal-1",
      "title": "Stop Burning Coal",
      ...
    },
    ...
  ],

  "meta": {
    "current_page": 1,
    "total_pages": 12,
    "previous_page": null,
    "next_page": 2
  }
}

Get a paginated list of all petitions, including ones that are unlaunched or otherwise not visible to the public. Includes all the same data as the single-petition endpoint for each petition.

GET /api/v1/petitions?page=1

Update

Updates petition content using an HTTP PUT request.

We support several request formats for the data to be updated, but the most straightforward is to send the data as a json formatted PUT body.

In the request body only include the fields that you would like to update to a new value. The slug in the URL defines which petition should be updated.

PUT /api/v1/petitions/no-taxes-on-tea

PUT request body

{
  "petition": {
    "signature_count_add_amount": 100
  }
}

Signatures

Signatures are actions that people have taken on Petitions. All signature actions are namespaced by URL underneath the petitions that the signature was recorded against.

Lookup

Find a member by email address. Once you have obtained a member id you can use this identifier in other API calls.

GET /api/v1/petitions/no-taxes-on-tea/signatures/lookup?email=foo@bar.com

Update email opt in type

Updates the email opt in type associated with a single signature. This can be used to change the email opt in type if a more legally compliant form of consent was gathered elsewhere. The email opt in type for the signature is updated to the opt in type specified by external_id.

No history is stored recording the original consent type, it is replaced by the new one specified in the API call.

POST /api/v1/petitions/no-taxes-on-tea/signatures/123

POST body

external_id=foo

Destroy

DELETE response body for Destroy

{
  "signature": {
    "deleted": true,
    "id": 123,
    "email": "foo@bar.com"
  }
}

Permanently deletes the signature with the specified ID. Note that this will decrement the signature count on the petition.

DELETE /api/v1/petitions/no-taxes-on-tea/signatures/123

List

List the signatures that are part of a Petition.

This is a paginated response. You can advance to the next page using the page parameter, which accepts a page number integer. By default the first page is returned.

Hash Description
meta pagination information
petition basic information about the petition
signatures an array of signature results

GET /api/v1/petitions/no-taxes-on-tea/signatures?page=2

Partnerships

Partnerships are other organisations you collaborate with

List

GET response body

{
  "partnerships": [
    {
      "slug": "fight-fascism",
      "title": "Fight Fascism",
      "description": "Fighting fascism one day at a time. With petitions.",
      "external_id": "no-fascism-123",
      "created_at": "2015-12-02T01:43:17Z",
      "updated_at": "2015-12-02T01:43:17Z",
      "url": "https://demo.controlshiftlabs.com/partnerships/fight-fascism",
      "image": {
        "urls": {
          "open_graph": "http://cdn.example.com/partnerships/images/25/open_graph/a-little-teapot.png?1516647704",
          "horizontal": "http://cdn.example.com/partnerships/images/25/horizontal/a-little-teapot.png?1516647704",
          "hero": "http://cdn.example.com/partnerships/images/25/hero/a-little-teapot.png?1516647704",
          "original": "http://cdn.example.com/partnerships/images/25/original/a-little-teapot.png?1516647704",
          "form": "http://cdn.example.com/partnerships/images/25/form/a-little-teapot.png?1516647704"
        }
      }
    },
    {
      "slug": "save-whales",
      "title": "Save the Whales",
      "description": "We're devoted to all policies that are good for whales.",
      "external_id": null,
      "created_at": "2018-01-02T12:32:45Z",
      "updated_at": "2018-12-22T01:43:17Z",
      "url": "https://demo.controlshiftlabs.com/partnerships/save-whales",
      "image": null
    }
  ],
  "meta": {
    "current_page": 1,
    "total_pages": 1,
    "previous_page": null,
    "next_page": null
  }
}

Get a paginated list of all partnerships.

GET /api/v1/partnerships?page=1

Show

GET response Body

{
  "partnership": {
    "slug": "fight-fascism",
    "title": "Fight Fascism",
    "description": "Fighting fascism one day at a time. With petitions.",
    "external_id": "no-fascism-123",
    "created_at": "2015-12-02T01:43:17Z",
    "updated_at": "2015-12-02T01:43:17Z",
    "url": "https://demo.controlshiftlabs.com/partnerships/fight-fascism",
    "image": {
      "urls": {
        "open_graph": "http://cdn.example.com/partnerships/images/25/open_graph/a-little-teapot.png?1516647704",
        "horizontal": "http://cdn.example.com/partnerships/images/25/horizontal/a-little-teapot.png?1516647704",
        "hero": "http://cdn.example.com/partnerships/images/25/hero/a-little-teapot.png?1516647704",
        "original": "http://cdn.example.com/partnerships/images/25/original/a-little-teapot.png?1516647704",
        "form": "http://cdn.example.com/partnerships/images/25/form/a-little-teapot.png?1516647704"
      }
    }
  }
}

Find information about a partnership by URL slug.

GET /api/v1/partnerships/fight-fascism

Events

Events represent real-world or virtual meetings that members can RSVP to. RSVP records are called “attendees”.

List

GET response body

{
  "events": [
    {
      "slug": "deliver-the-petition-to-the-wizard",
      "title": "Deliver the Petition to the Wizard",
      "url": "https://demo.controlshiftlabs.com/events/deliver-the-petition-to-the-wizard",
      "description": "Join us as we travel to the Emerald City to deliver our petition to the Wizard.",
      "start_at": "2018-03-01T12:00:00Z",
      "end_at": "2018-03-01T13:30:00Z",
      "start_in_zone": "2018-03-01T12:00:00-05:00",
      "end_in_zone": "2018-03-01T13:30:00-05:00",
      "time_zone": "America/New_York",
      "virtual": false,
      "launched_at": "2018-02-03T12:34:56Z",
      "locale": "en-US",
      "host_address": null,
      "max_attendees_count": null,
      "external_id": "control-shift-deliver-the-petition-to-the-wizard",
      "location": {
        "query": "wizard room",
        "latitude": "42.3376368",
        "longitude": "-70.99304",
        "venue": "The Wizard's Throne Room",
        "street_number": "",
        "street": "",
        "region": "MA",
        "postal_code": "",
        "country": "US",
        "created_at": "2017-11-16T22:38:29Z"
      },
      "petition": {
        "slug": "repair-the-yellow-brick-road-1",
        "url": "https://demo.controlshiftlabs.com/petitions/repair-the-yellow-brick-road-1"
      },
      "labels": ["oz-organizer-help"]
    },
    ...
  ],
  "meta": {
    "current_page": 1,
    "total_pages": 1,
    "previous_page": null,
    "next_page": null
  }
}

Get a paginated list of all events, including ones that are unlaunched or otherwise not visible to the public. Includes all the same data as the single-event endpoint for each event.

GET /api/v1/events?page=1

Show

GET response Body

{
  "event": {
    "slug": "deliver-the-petition-to-the-wizard",
    "title": "Deliver the Petition to the Wizard",
    "url": "https://demo.controlshiftlabs.com/events/deliver-the-petition-to-the-wizard",
    "description": "Join us as we travel to the Emerald City to deliver our petition to the Wizard.",
    "start_at": "2018-03-01T12:00:00Z",
    "end_at": "2018-03-01T13:30:00Z",
    "start_in_zone": "2018-03-01T12:00:00-05:00",
    "end_in_zone": "2018-03-01T13:30:00-05:00",
    "time_zone": "America/New_York",
    "virtual": false,
    "launched_at": "2018-02-03T12:34:56Z",
    "locale": "en-US",
    "host_address": null,
    "max_attendees_count": null,
    "external_id": "control-shift-deliver-the-petition-to-the-wizard",
    "location": {
      "query": "wizard room",
      "latitude": "42.3376368",
      "longitude": "-70.99304",
      "venue": "The Wizard's Throne Room",
      "street_number": "",
      "street": "",
      "region": "MA",
      "postal_code": "",
      "country": "US",
      "created_at": "2017-11-16T22:38:29Z"
    },
    "petition": {
      "slug": "repair-the-yellow-brick-road-1",
      "url": "https://demo.controlshiftlabs.com/petitions/repair-the-yellow-brick-road-1"
    },
    "labels": ["oz-organizer-help"]
  }
}

Find information about an event by URL slug.

GET /api/v1/events/deliver-the-petition-to-the-wizard

Calendars

Calendars are collections of related events.

Show

GET response Body

{
  "calendar": {
    "name": "Movie Screenings For Healthcare Access",
    "description": "We're watching this amazing and powerful movie about the fight for health care for all.",
    "slug": "movie-screenings-for-healthcare-access",
    "url": "https://demo.controlshiftlabs.com/calendars/movie-screenings-for-healthcare-access",
    "partnership": {
      "slug": "space-captains-for-health-care",
      "url": "https://demo.controlshiftlabs.com/partnerships/space-captains-for-health-care"
    }
  }
}

Find information about a calendar by URL slug.

GET /api/v1/calendars/movie-screenings-for-healthcare-access

List Events

List the events that are part of a Calendar.

This is a paginated response. You can advance to the next page using the page parameter, which accepts a page number integer. By default the first page is returned.

Hash Description
meta pagination information
calendar basic information about the calendar
events an array of event results

GET /api/v1/calendars/movie-screenings-for-healthcare-access/events?page=2

Webhook Endpoints

Webhooks can be used by software engineers to integrate ControlShift with third-party systems. They allow engineers to build software that is triggered by events that take place within ControlShift. This authenticated endpoint provides status information about webhook endpoints.

This is useful for customers who would like to build automated reporting to detect when webhooks have been disabled or are setup incorrectly.

List Webhook Endpoints

Show information about all of the webhook endpoints configured for organisation

GET /api/v1/webhook_endpoints

GET response body for Index (List)

[
  {
    "id":1,
    "organisation_id":7,
    "url":"https://example.com/test1",
    "created_at":"2019-06-19T15:02:47Z",
    "updated_at":"2019-06-19T15:02:47Z",
    "disabled_at":null,
    "last_failure":null
  },
  {
    "id":2,
    "organisation_id":7,
    "url":"https://example.com/test2",
    "created_at":"2019-06-19T15:10:47Z",
    "updated_at":"2019-06-19T15:45:47Z",
    "disabled_at":"2019-06-19T15:45:47Z",
    "last_failure":"2019-06-19T15:45:01Z"
  },
]