Essentials

If you're new to APIs, you'll want to start with the Essentials to become familiar with the many possibilities Tracker's API offers.

  • Check out the Basics to learn how to provide authentication credentials for your requests.
  • Learn how you can customize your request's response, and get detailed information about associations such as story owners or project members, and control pagination for endpoints that support it.
  • Go find the endpoints you want to use here. Note that each endpoint has a link to the resource that it returns. The resource lists the properties of the information returned by the endpoint. Note that there isn't a 1:1 relationship between resources and endpoints.

Getting Started

Basics

Tracker provides an API that can be used to create, retrieve, update, and delete virtually all of the resources stored and manipulated by the system. This interface incorporates several elements of the REST tradition, making access to these actions consistent and intuitive.

In a nutshell: collections are accessed via a URL ending in a plural noun such as '.../stories', and to target an individual resource instance, append its numerical id (.../stories/85). To tell the API whether you are viewing, updating, or deleting the resource, make use of the "HTTP verbs," GET, PUT, PATCH, and DELETE. To create a new resource, send a POST request to the collection's URL (.../stories). The API's response will be the content of the requested resource(s) or the data from the as-updated resource.

This is an entirely new version of the API, expanded to cover much more of Tracker's functionality, using JSON as its primary encoding with some endpoints being able to encode responses in CSV format.

The Pivotal Tracker API allows client software to retrieve and operate on a user's data by making HTTP requests to transfer JSON-encapsulated data.

With a few exceptions, requests have to be authenticated with credentials for a specific Tracker user, on whose behalf the request is being made. Credentials are currently a single per-user API Token which users obtain from their personal Profile page.

Because the Tracker API is accessible by anything that can make HTTP requests, not only can API client software be written in almost any available programming language, but people can interact with it directly through any tool that's capable of making requests and receiving responses. The Tracker API documentation contains many examples written as command lines using the popular and widely-available utility curl.

In addition to software that runs under the user's OS or on a server somewhere, the Pivotal Tracker API supports requests originating from JavaScript executing within the user's browser via Cross-Origin Resource Sharing (CORS). Requests made by non-Tracker API clients from within a browser use the same authentication mechanism (user API tokens) as requests originating from anywhere else.

cURL Examples

This section contains several example operations that you can try immediately using curl. The only prerequisites are your Pivotal Tracker API token, that you have access to a project in Tracker, and that you know the ID number of that project. When viewing a project in Tracker on the web, the project's ID number is contained in the browser address bar.

Each example shows the curl command to create the request, and shows the server response when you click the associated "View Response" button. The response data is intended to be typical of a realistic example, rather than comprehensive. Most responses can vary significantly based on the specific content of the project, see Response Customization.

Operations on Stories

The story is the central type of resource managed by Tracker, though The Tracker Data Model lists them all. This section gives examples of the basic story operations.

Fetch a Single Story

The API offers several ways to get individual stories or groups of stories. The attributes of a single story can be retrieved using only the story's unique ID:

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/stories/555"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","current_state":"unstarted","description":"ignore the droids","estimate":2,"id":555,"kind":"story","labels":[],"name":"Bring me the passengers","owner_ids":[],"project_id":99,"requested_by_id":101,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:00Z","url":"http://localhost/story/show/555"}

However, the path for that request isn't structured according to RESTful conventions. Most API endpoints (paths) are composed of alternating (plural) resource names and ID numbers, based on which resources contain others. For example, projects contain stories, so the "RESTy" endpoint for fetching a story is built with the ID of the project that contains the story, and the story ID:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/559"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","current_state":"rejected","description":"ray shielded, that is.","estimate":3,"id":559,"kind":"story","labels":[{"id":2009,"project_id":99,"kind":"label","name":"plans","created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"}],"name":"All exhaust ports should be shielded","owned_by_id":104,"owner_ids":[104],"project_id":99,"requested_by_id":102,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:00Z","url":"http://localhost/story/show/559"}

The endpoints that allow the retrieval of multiple stories at once include /projects/{project_id}/iterations (fetch one or more of the iterations from the project, which can be returned with copies of all the stories in the iteration), /projects/{project_id}/search (which searches within the project for stories and epics that match a client query), and /projects/{project_id}/stories (fetch all or a subset of the stories in a project) shown in the next section.

Get Stories from a Project Using a Filter

GET'ing from /projects/{project_id}/stories endpoint will fetch all of the stories in a project. In addition, including the filter parameter will return only those stories from the project that match the search string supplied as the value of filter. Filter strings are interpreted in exactly the same way as search strings typed into the search box in the Tracker web UI, explained in the Tracker Help Center.

In this example, the client is requesting all stories from the project which have the label "plans":

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories?date_format=millis&filter=label%3Aplans"

Hide Response
Headers
Response Body
[{"kind":"story","id":559,"created_at":1710244800000,"updated_at":1710244800000,"estimate":3,"story_type":"feature","story_priority":"p3","name":"All exhaust ports should be shielded","description":"ray shielded, that is.","current_state":"rejected","requested_by_id":102,"url":"http://localhost/story/show/559","project_id":99,"owner_ids":[104],"labels":[{"id":2009,"project_id":99,"kind":"label","name":"plans","created_at":1710244800000,"updated_at":1710244800000}],"owned_by_id":104},{"kind":"story","id":561,"created_at":1710244800000,"updated_at":1710244800000,"story_type":"bug","story_priority":"p3","name":"Tractor beam loses power intermittently","current_state":"unstarted","requested_by_id":102,"url":"http://localhost/story/show/561","project_id":99,"owner_ids":[],"labels":[]}]

Note that this example also shows the date_format parameter (see Selecting the Date/Time Format) being used to request that the server return date/time values in the response encoded as a number of milliseconds since the epoch, rather than the default encoding, ISO 8601.

Create a New Story

To create a new story in a project using the API, the client POSTs the desired content to the /projects/{project_id}/stories endpoint. The story content must be in the request's POST body, encapsulated in a JSON hash. The server will supply default values for all of the story's attributes that are not provided in the request. This example shows the creation of a story using several parameters to create it already in the "started" state. The only story attribute that is required to be provided as a POST parameter is name.

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"current_state":"started","estimate":1,"name":"Exhaust ports are ray shielded 👹"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","current_state":"started","estimate":1,"id":2300,"kind":"story","labels":[],"name":"Exhaust ports are ray shielded 👹","owner_ids":[],"project_id":99,"requested_by_id":101,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:00Z","url":"http://localhost/story/show/2300"}
Update a Story Description

To change an attribute on an existing story, the client performs a PUT request to the endpoint /projects/{project_id}/stories/{story_id}. This example replaces the story's description with a new value, leaving the story's other attributes unmodified (except for updated_at, which always reflects when the resource was last changed).

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X PUT -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"description":"I want them alive. Bring them to me in order of seniority. Don't miss anyone!"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555"

Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","current_state":"unstarted","description":"I want them alive. Bring them to me in order of seniority. Don't miss anyone!","estimate":2,"id":555,"kind":"story","labels":[],"name":"Bring me the passengers","owner_ids":[],"project_id":99,"requested_by_id":101,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:05Z","url":"http://localhost/story/show/555"}

Note: Unless stated otherwise for a specific operation, the client may use the HTTP method PATCH in place of PUT for any request. See _method Parameter for Request Method Simulation for details.

Add a Comment To a Story

Just as projects contain stories, each story has a set of comments. To create a new comment for a story, the client POSTs to the /projects/{project_id}/stories/{story_id}/comments endpoint. The endpoint's path makes it clear which story is receiving the new comment, and the parameters of the POST (again, in the JSON-encoded body) give the content for the new comment:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

export STORY_ID=555

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"text":"If this is a consular ship, then where is the ambassador 👅?"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/$STORY_ID/comments"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","id":300,"kind":"comment","person_id":101,"story_id":555,"text":"If this is a consular ship, then where is the ambassador 👅?","updated_at":"2024-03-12T12:00:00Z"}
Add a Comment To a Story with an Attachment

Comments on stories can contain text and/or attached files. Creating a comment with just text is relatively simple, as above. Attaching files is more complicated; each attachment must be uploaded to Pivotal Tracker by a separate request first, and then the comment that attaches all of them together to the story is created. An attachment file upload can be performed as follows:

export TOKEN='your Pivotal Tracker API token'

export FILE_PATH='/home/vader/art-projects/new-imperial-logo-6.jpg'

export PROJECT_ID=99

curl -X POST -H "X-TrackerToken: $TOKEN" -F file=@"$FILE_PATH" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/uploads"

View Response
Hide Response
Headers
Response Body
"{\"kind\":\"file_attachment\",\"id\":300,\"filename\":\"new-imperial-logo.jpg\",\"created_at\":\"2024-03-13T12:00:00Z\",\"uploader_id\":101,\"thumbnailable\":true,\"height\":1995,\"width\":2000,\"size\":96228,\"download_url\":\"/file_attachments/300/download\",\"content_type\":\"image/jpeg\",\"uploaded\":false,\"big_url\":\"#\",\"thumbnail_url\":\"#\"}"

The /projects/{project_id}/stories/{story_id}/comments endpoint accepts an optional file_attachments parameter, which must be an array of hashes, where each hash contains the attributes on an attachment resource. The response to each successful POST to /projects/{project_id}/uploads endpoint is the content of the attachment resource created. To create a comment with one more more associated file attachments, all of the uploads operations should be performed first, with the client collecting the results from each one. And then perform the POST to create the comment, including each of the received attachment hashes in the file_attachments array, as follows:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

export STORY_ID=555

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"file_attachments":[{"id":24,"kind":"file_attachment","filename":"empire.png","size":82382,"width":1000,"height":804,"uploader_id":100,"thumbnail_url":"/attachments/0000/0024/empire_thumb.png","thumbnailable":true,"uploaded":true,"download_url":"/attachments/0000/0024/empire_big.png","created_at":"2024-03-12T12:00:00Z","content_type":"image/png"}],"text":"What if this were our new sigil?"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/$STORY_ID/comments?fields=%3Adefault%2Cfile_attachment_ids"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","file_attachment_ids":[24],"id":300,"kind":"comment","person_id":101,"story_id":555,"text":"What if this were our new sigil?","updated_at":"2024-03-12T12:00:00Z"}
Delete a Story

Clients can delete individual stories by issuing an HTTP DELETE request to the /projects/{project_id}/stories/{story_id}/comments endpoint:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X DELETE -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555"

Hide Response
Headers
Operations on Projects
Fetch a Single Project

Retrieving the attributes of a project is accomplished by making a GET request to the /projects/{project_id} endpoint.

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/99"

Hide Response
Headers
Response Body
{"account_id":100,"atom_enabled":true,"automatic_planning":true,"bugs_and_chores_are_estimatable":false,"created_at":"2024-03-12T12:00:05Z","current_iteration_number":15,"description":"Expeditionary Battle Planetoid","enable_following":true,"enable_incoming_emails":true,"enable_tasks":true,"has_google_domain":false,"id":99,"initial_velocity":10,"iteration_length":1,"kind":"project","name":"Death Star","number_of_done_iterations_to_show":4,"point_scale":"0,1,2,3","point_scale_is_custom":false,"profile_content":"This is a machine of war such as the universe has never known. It's colossal, the size of a class-four moon. And it possesses firepower unequaled in the history of warfare.","project_type":"private","public":false,"show_priority_icon":false,"show_priority_icon_in_all_panels":true,"show_story_priority":true,"start_date":"2023-11-27","start_time":"2024-03-12T12:00:10Z","time_zone":{"kind":"time_zone","olson_name":"America/Los_Angeles","offset":"-07:00"},"updated_at":"2024-03-12T12:00:10Z","velocity_averaged_over":3,"version":66,"week_start_day":"Monday"}
Get a Project's Version

Every project has a version number, which represents the count of changes that have been made to the project since its creation. The project version is central to how Tracker ensures that all clients viewing the same project receive and integrate changes as they are made. When a project is retrieved from Tracker, as in the example above, the version is (or can be) included. In addition, each time a request is made that accesses a project or a resource within a project, the project's current version is returned either in the HTTP response header X-Tracker-Project-Version or in the Envelope Return Structure if the client has requested that.

If the client has not already received a project's version number through a prior request, or it has been long enough since then that the client wants to get the current version, it can be obtained through a project request. The fields parameter (see Selecting Attributes to Include in Responses can be used to make Tracker API responses lighter-weight by having them include only what the client is interested in. The following request fetches a project, like the preceding example, but the client requests that only the project's version be returned in addition to the minimum structure.

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/99?fields=version"

Hide Response
Headers
Response Body
{"id":99,"version":66}
Notes on cURL Examples

In most of the request examples, above and in the rest of these documentation, the actual curl command is preceded by shell exports that set variables that are incorporated into the curl request. The export statements shown in the examples contain either constants consistent with the example or place-holders intended to indicate what a valid value would be. However, if you define the same variables on your system, but with data that matches your authentication, project and other IDs, etc., then you should be able to execute the curl commands directly without modification, simply by copying and pasting. The lines for initializing such variables with realistic values should look like the following (obviously the API token value shown is invalid).

  export TOKEN=8cef9ce9f959c4348742c1595f340836
  export PROJECT_ID=99

Most of the examples show authentication using an API token passed through the HTTP request header X-TrackerToken. This, and other request headers that are not automatically included by the curl command are shown in the command line as arguments of the -H option. However, there are a number of request headers that curl does include automatically. If you find yourself having trouble reproducing the operation of one of these examples using another command or inside of a program, don't forget that the Tracker API may be expecting/requiring request headers that curl provides but that are not part of another system by default.

One case of this is the /projects/{project_id}/uploads endpoint, where the example relies on curl creating a Content-type header that matches the multi-part request body that it generates, both of which will have to be done by any API client that uses this endpoint.

Browser Example

With CORS, Tracker API requests can also be performed by JavaScript executing in your browser. The following is a trivial HTML page, which you can copy-and-paste into a file on your system or a static-file web server, and then open in your browser. It uses the jQuery JavaScript library to simplify the operations of making the request to Tracker and updating the page with the response.

The example includes a form so that you can enter your Pivotal Tracker API token and the ID number of a project to which you have access. Then, when you click the link, it performs a GET to the /projects/{project_id}/stories endpoint, with parameters that request up to the first 20 unaccepted stories in your project.

<html>
  <head>
    <title>Access the Pivotal Tracker API via CORS</title>
    <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js'
            type='text/javascript'></script>
  </head>
  <body>
    <form>
      <p>
        <label for='token'>User API Token:</label>
        <input type='text' id='token' />
      </p><p>
      <label for='project_id'>ID Number of Pivotal Tracker Project:</label>
      <input type='text' id='project_id' />
    </p>
    </form>

    <a href='#' id='doit_link'>Fetch Story Names from Project</a>

    <div style='margin:40px'>
      <p id='result_title'></p>
      <ul id='result_area'>
      </ul>
    </div>

    <script type="text/javascript">
      var projectId;

      function executeTrackerApiFetch() {
        // get parameters
        var token = $('#token').val();
        projectId = $('#project_id').val();

        // compose request URL
        var url = 'https://www.pivotaltracker.com/services/v5';
        url += '/projects/' + projectId;
        url += '/stories?filter=state:delivered,finished,rejected,started';
        url += ',unstarted,unscheduled';
        url += '&limit=20';

        // do API request to get story names
        $.ajax({
          url: url,
          beforeSend: function(xhr) {
            xhr.setRequestHeader('X-TrackerToken', token);
          }
        }).done(displayTrackerApiResponse);
      }

      function displayTrackerApiResponse(stories) {
        $('#result_title').html('Unaccepted stories from Project #' + projectId + ' (up to the first 20)');

        var html = '';
        for (var i=0; i < stories.length;i++) {
          html += '<li>' + stories[i].name + '</li>';
        }
        $('#result_area').html(html);
      }

      $(function() {
        $('#doit_link').click(executeTrackerApiFetch);
      });
    </script>
  </body>
</html>

If you are going to load this example HTML in a browser directly from a file stored on your local system (file: scheme), consider using the Safari browser. Neither Chrome nor Firefox will originate a CORS request from file:, though they will from a static file served from http://localhost/.

The Tracker Data Model

Pivotal Tracker stores user data using a number of special-purpose resource types, each of which has a particular set of attributes, including its relationship to the other resources. The Resources section provides a detailed reference of each type of resource. This section lists the most-used resources.

All resources in Tracker have either a unique integer ID or (for the iteration and iteration_override models) a number that identifies them within the scope of their project. The relationships between resources are represented by attributes on one resource that contain a, or a list of, the ID(s) or number(s) of the associated resource. Some associations are represented by attributes on both resources, referencing each other, but others are defined by attributes on only one end of the relation.

The major Pivotal Tracker API resource types are:

story

Stories are the heart of Tracker. For a software development team, stories are the description of what the people will be able to do with your system once the work is completed. For other uses of Tracker, stories are the basic "work to do" item. Virtually all of the other resources can be viewed simply as meta-data about stories, information about individual stories or information about how the stories are grouped.

project

In Pivotal Tracker, a project represents the combination of a group of stories (work to do) and the team that will be working on them (the people who are "members" of the project). Projects also contain the information about how the work has proceeded in the past and a best guess about how it is expected to unfold in the near future.

label

Labels are used to mark stories that are associated, share a common attribute, or are all members of the same epic. The project contains the full set of available labels, and labels have to be created within the project before they can be applied to a story or an epic. (Some API endpoints automatically create labels as needed, when requested to apply a not-yet-existing label to another resource, so that clients do not have to perform the label create operation separately.)

Stories and epics are associated with the labels applied to them through attributes on the stories/epics (only), with a label_ids array on each story and a label_id on each epic.

task

Each story may have a list of tasks. Each story's tasks are unique to that story, they are not shared as labels are.

comment

Each story may also have a list of comments. Like tasks, the comments are owned (contained) by a single story. Unlike tasks, comments can contain (be associated with) other resources, representing attachments made to the comment.

file_attachment

When a file is uploaded to Tracker and attached to a story by creating a comment, a file_attachment resource is created to contain the metadata describing the attachment, and fetching the resource provides the client with the URL necessary for downloading the attached file itself.

google_attachment

When a Google Drive file is attached to a comment in Tracker, a google_attachment resource is created to provide a representation of it in the Pivotal Tracker system.

epic

Epics are an organization mechanism for stories, intended to provide a container for all of the stories associated with an overall system feature or product initiative. Each epic is associated with a single label, and all of the stories marked with that label are contained by the epic. The project contains a list of all its epics, each epic references its label, and the client can request all of the stories that are marked with a particular label.

person

A person resource represents an individual Tracker login and profile. The same person information is available (through the project_membership resource) from each project that the same person is a member of.

project_membership

A project_membership represents the relationship between an individual person and one project that they are a member of.

iteration

An iteration is a period of time over the course of work on a project. Iterations can be either in the past or in the future. Each iteration has a number, but there is not a fixed relationship between an iteration number and the iteration's start and end dates. For example, iteration number 1 is always the first iteration in a project, even if something causes the project's start date to change occurs. And the number of iterations in the project (and therefore the largest valid iteration number) will vary over time. In particular, iterations are created implicitly as stories are moved from the Icebox to the Backlog, so that every non-Icebox story can be placed into an iteration that has a valid number.

iteration_override

The iteration_override resource is used to manipulate configuration attributes of the underlying iteration, associated by having a matching number. While iterations exist in a contiguous, fully-populated set, iteration_override resources exist only for iteration numbers that have had their configuration attributes set to a non-default value.

integration

An integration resource contains the data that is necessary for Tracker to exchange information with a third-party system. They are typically created in the Pivotal Tracker web user interface by selecting the INTEGRATIONS tab in a project's settings pages, and can be used through the API to get the integration's list of external_story resources, see /projects/{project_id}/integrations/{integration_id}/stories. These are the story-like entries that are shown in the integration panels that can be opened within the Tracker web user interface, and used to create Pivotal Tracker stories from data imported from the third-party system.

NOTE: Few account level & project level integrations such as GitHub, GitHub Enterprise, Gitlab, Gitlab Self-Managed and Concourse Pipeline currently can only be managed via the Generic_Integrations endpoint. Please refer the above endpoint for more details.

account

accounts are resources that serve as containers for a group of related projects, and provide for that group of projects to all be covered by the same paid subscription to Pivotal Tracker.

The Tracker API

This section describes the basic conventions of the Tracker API, including the supported fundamental data types, URL structure, and data encoding. Lower-level details are addressed under Response Customization, below.

Tracker API Conventions

Each response will contain data that describes one of the API Resources, or an array of resource data structures. The keys that can occur within a resource structure are given in the matching section of this documentation. However, new keys may be added at any time without an increase in the Tracker API version as long as no existing keys are modified, so clients must be capable of ignoring any resource attribute that they are unable to process.

API Data Types
boolean

The value is either true or false. In JSON-encoded requests and responses, a boolean value is represented with the bare symbols true and false. Attributes that are boolean are never "empty," so they will not be dropped from server responses like other attributes can be.

string

This type of value is a string of text characters. Tracker expects/uses UTF-8 charset strings, and values of this type support up to encoding with up to three bytes per character. Most string-type parameters have a maximum character count. The length limit is represented in the Resources reference by a number, enclosed in square brackets, added to the end of the type name. For example, string[120] indicates a string with a maximum length of 120 characters. In JSON-encoded requests and responses, strings must be enclosed in quotation marks and employ conventional JSON (JavaScript) character escaping and encoding.

extended string

Similar to string values, but supports characters with four-byte UTF-8 encodings, such as emoji.

enumerated string

Enumerated strings are like strings, except that the content of the string is constrained to a predefined set of possible values. Each parameter or attribute that has the type "enumerated string" is accompanied by a "valid enumeration values" list that shows all of the legal values for that particular enumerated string. Values must be exactly as given, including matching case and without any padding/white space.

int

An integer value. Integers are decimal, and some may be zero and/or negative. Most integer parameters/attributes must be 1 or larger. The requirements for a particular integer value should be clear from context, and the API will return error messages if not.

float

A real number. Encoding must conform to the JSON syntax.

datetime

A representation of a particular date and time. In general, datetime values can be expressed in one of two ways. They may be a string encoded according to the ISO 8601 standard, like "2013-04-30T04:25:15Z". Or they will be an integer number of milliseconds since the beginning of the epoch, like 1367296015000. When supplying a parameter value that is a datetime, the client may use either format and Tracker will determine which is being used dynamically. By default, datetime values included in a response will be ISO 8601 strings. There are a handful of responses in which resources have datetime attributes which are only able to be formatted in one of these ways. This fact is noted in the descriptions of those particular attributes. For the majority, to cause dates to be formatted as millisecond offsets instead, supply the parameter date_format=millis in the query string of the request URL.

datetime_interval

A representation of a particular date and time range. It is string encoded using the ISO 8601 interval standard, like "2013-04-29T04:00:00Z/2013-04-30T04:00:00Z".

date

date values are encoded according to the ISO 8601 standard, like "2013-04-30". date-type attributes do not have a time incorporated in them like datetime attributes do.

scalar

This indicates that the value is type-checked as being one of the preceding types, but it may be any one of those types at different types. For example, a resource attribute whose type is List[scalar] can contain a mix of differently-typed values. The actual type of each value can be determined interactively or may be established by contention for a particular API request or use.

object

This indicates that the value is an un-type-checked nested hash data structure. For both request parameters and response attributes listed as being object type, their expected/possible content should be clear from context.

The same set of types is used for the parameters that may be supplied in API requests, and the parameters to individual Commands.

Request Parameter Encoding

Parameters are supplied as parts of Pivotal Tracker API requests in a number of ways:

  • As components of the request URL's path. These are marked as path parameters in the endpoint reference, and are typically ID numbers or other integers.
  • In the request URL's query string (the portion following the question mark, ?). These are marked as query parameters in the endpoint reference. Parameters that are expected to be passed in the query string cannot be passed in the request body (and vice versa).
  • In the body of non-GET requests. These are marked as body parameters in the endpoint reference. When a body is provided with a Tracker API request, it must be encoded using JSON unless a specific endpoint description specifies another encoding.
  • In custom headers in the HTTP request itself. These are not listed as parameters of individual requests, but are common across requests and described under Common HTTP Extension Request Headers.

Body parameters can be simple scalars, arrays, nested resource structures, or arrays of structures. Arrays, either of integer ID values or of nested structures are used to represent associations between one resource and several others.

When a resource structure is used as the value of a parameter, the client has some flexibility in choosing what attributes to include in the nested resource structure it creates. If the client intends to reference an existing resource in the system, then it must supply sufficient information to uniquely identify the existing resource (the id value is usually easiest/best, but things like the name attribute for labels are also unique identifiers). Any additional information that is included must match the existing values of the sub-resource. If the client intends to implicitly create a new resource, it must not include the id parameter (the server will be assigning a new id) and it must supply any attributes for which the server cannot supply a default value.

'Content-Type' will be required, and required to be 'application/json'. In order to ensure forward compatibility with API extensions that allow request bodies to be encoded in a format other than JSON, we will be imposing an error check that requires clients to explicitly specify the format of the request body by including the Content-Type header in the request.

Common Request Parameters
token Parameter for Authentication

Requests that are authenticated (see Authenticating Using API Tokens) have to pass the API token for the user on whose behalf the client is operating to the server with each request. The preferred method is to send in API token n the HTTP request as the value of the X-TrackerToken header. However, it is also possible to use the query parameter token in the URL of a request to pass the API token value.

envelope Parameter

The Tracker API can return responses in two different formats: just the raw resource data with any metadata passed in HTTP response headers (the default), or the resource data embedded as a single element of an 'envelope' structure that contains any metadata in the response body.

This is controlled by the envelope parameter being included in the request URL's query string. Requests with no envelope parameter or envelope=false return the default format. To embed the response resources in the envelope, the client should make its request with envelope=true. The structure of the response envelope is described in Envelope Return Structure, below.

_method Parameter for Request Method Simulation

To use all of the operations available in the Pivotal Tracker API, clients must be able to generate the HTTP methods GET, POST, PUT, PATCH, and DELETE. However, some older network infrastructure can only generate GET and POST. If you are implementing a client in such an environment, PUT, PATCH, and DELETE requests can be simulated by making POST requests that include the query parameter _method. The permissible values for _method are "PUT", "PATCH", and "DELETE" only. Clients that are capable of generating correct request methods should not use this parameter.

_method cannot be used as a POST-body parameter. Supplying a non-empty POST body is inappropriate for DELETE operations, whether performed using the actual DELETE method or simulating it by performing a POST request with _method=DELETE.

Note that while the Tracker API supports clients which use the HTTP PATCH method to update resources, it does so by making PUT and PATCH synonymous. While it is technically more correct to use PATCH when updating a subset of the attributes of a resource, and use PUT only in the case where the entire content of the resource is replaced, Tracker supports partial-update through PUT as well. Where the reference description of an individual endpoint references PUT, either PUT or PATCH may be used.

The API also accepts HTTP requests using the OPTIONS method to support in-browser JavaScript clients using Cross-Origin Resource Sharing. OPTIONS is another method that isn't universally supported. However, the _method parameter cannot be used to simulation an OPTIONS request. Because CORS is a browser-to-server protocol, that isn't meaningful to non-browser clients, there is no value in clients that are not CORS-capable browsers issuing requests using OPTIONS.

Common HTTP Extension Request Headers
X-TrackerToken Header for Authentication

Requests that are authenticated (see Authenticating Using API Tokens) have to pass the API token (for the user on whose behalf the client is operating) to the server with each request. The preferred method is to send in API token in the HTTP request as the value of the X-TrackerToken header. If this request header is not used, the token request parameter must be.

X-Tracker-Warn-Unless-Project-Version-Is Header

When used, the value passed with this header should be the integer project version for the Tracker project resource referenced by the request. The project version is increased each time a change is made to the content of the project, and the version number can be used by a client to keep track of whether the data stored on the server has changed since the client has most recently retrieved information. The version number can be obtained from any request that provides information from the project.

This header will be ignored if it is included in a request that does not identify a single project, i.e., only requests that somehow include a project_id parameter will observe X-Tracker-Warn-Unless-Project-Version-Is.

For endpoints that support it, when Tracker receives a request with the X-Tracker-Warn-Unless-Project-Version-Is header, it will either include the header X-Tracker-Warning-Project-Was-Stale in the HTTP response, or it will include the key warning_project_was_stale in the response envelope, depending on whether envelope=true is included in the request's query parameters, see Envelope Return Structure.

During request processing, Tracker compares the project version number passed in as the header's value with the selected project's current version. If the two match, the value returned (in the response header or envelope) will be false (no warning), and if the version numbers don't match, it will be true. This allows a client that is keeping a local copy of some or all of the project's data to know whether it's store remains valid, or if it might be out of date relative to what's stored on the server (and presumably also in other clients). A client could always poll to determine this, by making one of the GET requests that returns a project version number. However, polling is expensive and wasteful, especially if the client doesn't have to be assured that it is up to date prior to making other requests.

There are a handful of endpoints which are project-specific but do not support the X-Tracker-Warn-Unless-Project-Version-Is header. These include /source_commits.

Response Structure

Responses from the Pivotal Tracker API share a number of structural details and conventions, explained in this section. In addition, there are a number ways in which the client can request that the responses be customized. These are described under Response Customization, below.

Default Return Structure

The Pivotal Tracker API can return the results of a request to the client either as the raw requested information (by default) or wrapped in a JSON envelope structure, see the section Envelope Return Structure just below.

The response to each API request will conform to the specified one of the structures documented under Resources if the request was successful, or to the structure described under Error Responses if an error occurred or was detected.

When the client doesn't request that the response be wrapped in an envelope, metadata associated with the client/server transport, or which characterizes the response data, is returned to the client in custom HTTP response headers.

The optional response headers that may be found in a Pivotal Tracker API response are:

X-Tracker-Project-Version

This is included when the response contains information that is specific to a single Pivotal Tracker project. The value associated with this key is identical to the version attribute of the project resource: an integer that indicates how many changes had been made to the project prior to when the data in the response was captured. The project version number can be used to determine if the responses from successive API requests reflect a consistent view of the project, or if it was changed by some other client in between requests. Project version is also used to allow a number of clients to synchronize their views of the shared project.

X-Tracker-Downtime-Start X-Tracker-Downtime-Finish

This is included when there is an upcoming scheduled maintenance downtime for the Pivotal Tracker service that will affect service of API requests. A client can use this to inform users of the upcoming availability. The value is a hash that indicates the planned start and end times of the maintenance window, see the example below.

X-Tracker-Deprecation-Warning

This will be in the future if the request that has generated the response, or an attribute contained in the response, has been deprecated. The value will contain an English string. The value may begin with a datetime value encoded as an ISO 8601 string (only). If the datetime is present, it will be separated from the string by a pipe character (|), and indicate the time after which the deprecated behavior/endpoint/etc. may be removed from the system.

X-Tracker-Pagination-Limit X-Tracker-Pagination-Offset X-Tracker-Pagination-Total X-Tracker-Pagination-Returned

Some combination of these keys will be present when the response contains a paginated list, rather than a representation of a single resource. They inform the client of what portion of the available set of items (the logical list selected by the request) has actually been returned. The client can affect what portion of the list is returned by an individual request using the standard pagination parameters. For details about the meaning of each of these headers' values and the pagination parameters see Paginating List Responses.

Envelope Return Structure

All responses from the Pivotal Tracker API may be wrapped in a common top-level structure, a standard "envelope" around the data that is otherwise returned from the particular request that was made. This section describes the envelope structure and gives examples of various successful responses. The way error information is encoded, and is encapsulated in the envelope structure is described in Error Responses, below.

The following is an example of an API response that is carrying (minimal) data from a single Pivotal Tracker story:

{ "http_status": "200", "project_version": 42, "data": { "id": 501, "name": "Install cargo elevator platforms in docking bays." } }

This shows a typical envelope structure, but there are a number of optional attributes in the envelope structure, including some of what's shown above. A successful response will always have:

  • A data key, under which is the structure that would ordinarily be the entire server response. In the example above, the hash under data contains attributes from a story resource.
  • http_status: The value of this key is the same as the content of the HTTP response's numeric status string. It is provided here for the convenience of client's that find it difficult to access the response's status.

In addition, the value from each of the Pivotal Tracker HTTP response extension headers listed above will be moved to an entry in the response envelope. Note that, in the envelope structure, several of the values are consolidated together into nested hashes, rather than being a collection of scalars as the HTTP header mechanism necessitates. The mapping is as follows:

  • X-Tracker-Project-Version's value goes to "project_version":
  • X-Tracker-Downtime-Start's value goes to "downtime": { "start": }
  • X-Tracker-Downtime-Finish's value goes to "downtime": { "finish": }
  • X-Tracker-Deprecation-Warning's value goes to deprecation_warning
  • X-Tracker-Pagination-Limit's value goes to "pagination": { "limit": }
  • X-Tracker-Pagination-Offset's value goes to "pagination": { "offset": }
  • X-Tracker-Pagination-Total's value goes to "pagination": { "total": }
  • X-Tracker-Pagination-Returned's value goes to "pagination": { "returned": }

This example API response shows some of the optional entries described above. (Assume this was generated by a request with ?fields=id&date_format=millis.)

{ "http_status": "200", "project_version": 420, "data": [ { "id": 1790 }, { "id": 1941 }, { "id": 5203 }, { "id": 11078 } ], "downtime": { "start": 1361473200000, "finish": 1361473590000 }, "pagination": { "total": 4, "limit": 50, "offset": 0, "returned": 4 } }
Successful Responses

When a request to the Tracker API is successfully processed, the status returned will be in the 2xx or 3xx ranges, consistent with the HTTP protocol. Note that this includes 204 (No Content). Several requests do not currently have a defined response structure, and do not produce a response body when successful (unless the client has requested the response envelope). This includes the responses to DELETE-method requests, as well as some others. For example, note how the description of DELETE-method requests to the stories endpoint (/projects/{project_id}/stories/{story_id}), does not include a statement about what resource is included in the response.

Currently POST operations which create new resources on the Tracker server return 200. They will be updated to return 201 (Created).

Error Responses

If an error occurs while processing an API request, Pivotal Tracker will return a hash of information regarding the type of failure, it's cause, and possibly how it can be corrected by the user (different data input) or the API client developer (different structure or sequence). The error response hash will either be the entire response or will be nested within the response envelope if the envelope parameter was used to request one. When an error is returned within the response envelope structure, it will be stored under the key data, where a successful response's content is also located. For example, this is an error-producing request without the response envelope:

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/stories/999999"

Hide Response
Headers
Response Body
{"code":"unfound_resource","error":"The object you tried to access could not be found. It may have been removed by another user, you may be using the ID of another object type, or you may be trying to access a sub-resource at the wrong point in a tree.","kind":"error"}

And this is a different error case, with the response envelope requested:

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/stories/1?envelope=true"

Hide Response
Headers
Response Body
{"data":{"code":"unauthorized_operation","kind":"error","error":"Authorization failure.","general_problem":"You aren't authorized to access the requested resource.","possible_fix":"Your project permissions are determined on the Project Membership page. If you are receiving this error you may be trying to access the wrong project, or the project API access is disabled, or someone listed as the project's Owner needs to change your membership type."},"http_status":"403"}

That an error has occurred may be determined either by examining the HTTP status of the response or by interrogating the kind key found under data. Note that, if you are going to use the value of kind, your client has to be prepared for the possibility that data will contain a single data structure (hash) in cases where it would ordinarily be expected to always be an array. For example, performing a GET request to the /projects/{project_id}/stories endpoint will always return an array (even if it's an empty one) for a successful request. But is will return a single error-information hash on failure.

This same error structure is used for all error cases. There are currently seven keys defined, the first three of which will always be returned:

kind

Error hashes, like other response resources, include a kind attribute to identify their type, independent of the context in which they're returned. Unlike other API responses, because the fields parameter cannot be replied to error responses, kind will always be included.

code

code is a single "word" string that identifies the class of error that has occurred or been detected. The set of currently defined code strings is listed below. code is always included in an error response.

error

This is an English string describing the category of the error which occurred. There is a one-to-one match between the code value and the error string for a particular error. error is always included in an error response.

requirement

An English string describing the constraint on the API that wasn't met by the current request.

general_problem

An English string with a broader/alternative description of the problem.

possible_fix

An English string with hints to the API client developer. Consider this a micro-FAQ answer specific to the particular error case. Very unlikely to be suitable for direct presentation to a user.

validation_errors

In the case where the server detected one or more errors with regard to the value of specific individual parameters, rather than with the request as a whole, the error response hash may be augmented with parameter-specific error messages. These messages are packaged in an array of hashes, stored under the validation_errors key. Each validation error hash contains a field key with the name of a request parameter (resource attribute), and a problem key with an English string describing the error(s) detected in the value of that parameter.

This example shows an invalid story-creation request, and how the server might respond to it:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"deadline":"2024-03-12T12:00:00Z","estimate":17,"name":"Build out entire planet-destroying laser"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories"

View Response
Hide Response
Headers
Response Body
{"code":"invalid_parameter","error":"One or more request parameters was missing or invalid.","general_problem":"17 is not a valid estimate for this project's point scale., Deadline can only be set on releases.","kind":"error","validation_errors":[{"field":"estimate","problem":"17 is not a valid estimate for this project's point scale."},{"field":"deadline","problem":"Deadline can only be set on releases."}]}

When the validation_errors array is present, there may or may not also be any of the other three optional error hash keys. A particular field key value (parameter name) will occur only once in the validation_errors array. If the value provided fails multiple constraints at once, all of the error information will be included in a single problem string. This is intended to allow clients to display problem strings directly to the user, if desired, and to closely associate them in the user interface with the particular controls that are providing values to the field(s) with problems.

The set of code values that Pivotal Tracker currently returns is: api_external_error, api_internal_error, cant_parse_json, capability_access_denied, component_unavailable, content_format_not_found, could_not_parse_body, deadlock, duplicate_entry, enterprise_signup_error, http_method_not_supported, https_required, integration_error, invalid_authentication, invalid_parameter, invalid_upload, not_acceptable, request_entity_too_large, requires_get, requires_post, route_not_found, server_under_load, timeout, unauthenticated, unauthorized_operation, unfound_resource, unhandled_condition, xhr_required.

However, the Tracker API may be upgraded at any time by the addition of a new error code, without an increase in the API version number (see API Versioning). Therefore, clients should be capable of handling an arbitrary code string gracefully.

Request Authentication and CORS

Most requests to the Pivotal Tracker API must be authenticated, using credentials that belong to a specific Tracker user login and profile. Clients should be constructed to obtain credentials from the user on whose behalf it is performing operations, so that correct attribution of changes is made. Some clients may be created with the intent of performing maintenance or monitoring or automatic updates on behalf of the entire team. In this case, it is recommended that a separate Tracker user account be created for exclusive use of the API client, rather than it being set up with the credentials of an existing person who is a member of the target project.

Requests that do not require authentication are (1) read operations (primarily GET requests) that are (2) performed on Public projects. The information from a Public project can be read by anyone on the Internet, whether they have a Pivotal Tracker login or not. This is the case in the Tracker web UI and through API requests.

Authenticating Using API Tokens

Each individual Pivotal Tracker login may be associated with a unique API Token value, which serves as a single-factor authentication for API requests. API tokens are allocated and copied from an individual's Profile page after logging into Tracker.

Once the user has obtained their Tracker API token, and provided it to the API client software, the client must include the token value in each API request it generates. There are two ways to pass the token through requests: as the value of the X-TrackerToken extension in the HTTP request's headers, or as the value of the token parameter in the query string portion of the request's URL. These are shown in the following two examples:

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/epics/5"

Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","description":"Identify the systems and eliminate the rebel scum.","id":5,"kind":"epic","label":{"id":2008,"project_id":99,"kind":"label","name":"rebel bases","created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"},"name":"Rebel Home Worlds","project_id":99,"updated_at":"2024-03-12T12:00:00Z","url":"http://localhost/epic/show/5"}

curl -X GET "https://www.pivotaltracker.com/services/v5/epics/5?token=VadersToken"

Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","description":"Identify the systems and eliminate the rebel scum.","id":5,"kind":"epic","label":{"id":2008,"project_id":99,"kind":"label","name":"rebel bases","created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"},"name":"Rebel Home Worlds","project_id":99,"updated_at":"2024-03-12T12:00:00Z","url":"http://localhost/epic/show/5"}
Authenticating via Basic-Auth to Obtain API Tokens

Authentication via API token works with all Tracker API requests. In addition, performing a GET request to the /me endpoint can be authenticated using HTTP Basic authentication. The /me endpoint retrieves information about the currently-authenticated user, including that user's API token. Thus, the /me endpoint provides a mechanism for a client to accept a user's login (email address or username) and password, and use the server to exchange that for an API token that can be used in subsequent API requests. Obviously, client software should not store the user's password at any point—only pass it to Tracker immediately after collecting.

If the authentication information provided to /me is incorrect, the response will use HTTP status code 403 (Forbidden). For example:

curl -X GET --user vader:bad_word "https://www.pivotaltracker.com/services/v5/me"

Hide Response
Headers
Response Body
{"code":"invalid_authentication","error":"Invalid authentication credentials were presented.","kind":"error","possible_fix":"Recheck your name (email address or Tracker username) and password."}

However, if the client supplies neither an API token nor the Basic auth name/password pair, the response will be HTTP status 401 (Unauthorized). This will allow clients to prompt users for their authentication credentials if they support this behavior. See the reference for the /me endpoint for examples of successful requests. A request responding with 401 to prompt the client to retry with authentication information would look like this:

curl -X GET "https://www.pivotaltracker.com/services/v5/me"

Hide Response
Headers
Response Body
{"code":"unauthenticated","error":"Needs authentication credentials.","kind":"error","possible_fix":"Try basic auth, or include header X-TrackerToken."}

Note that this behavior is prevented for XML HTTP Requests (XHRs, those where the request header contains "X-Requested-With: XMLHttpRequest"). For these requests, not supplying authentication credentials is treated the same way as supplying bad authentication, and results in a 403 response.

Cross-Origin Resource Sharing

CORS is a capability that applies only to Tracker API clients that are written to execute inside a web browser that is subjecting them to the Same Origin Policy.

In order to allow for Tracker API clients that are JavaScript programs executing within the user's browser, Pivotal Tracker supports Cross-Origin Resource Sharing when accessing API endpoint URLs. Tracker supports the user's browser sending "pre-flight" requests using the HTTP OPTIONS method, and it returns Access-Control-... headers that give the browser permission to make requests to the API.

While CORS allows JavaScript clients to access the Tracker API from within a browser, the client still must have the API token for a particular Pivotal Tracker user in order to make most requests (all requests that access the data of a private project). Because Tracker API tokens are a means of single-factor authentication, it is very important that they not be used or communicated in a way that would allow them to be copied by third parties. As with previous versions of the Tracker API, ensuring the security with which user API tokens are obtained and (if necessary) stored by client software is the responsibility of that software's authors. However, the fact that the source and runtime environment of a JavaScript API client are inherently public creates new opportunities for token-handling to be compromised.

A JavaScript client that obtained the API token from the user anew on each load/execution could provide a great deal of security, since the token would only ever be loaded in runtime environments that the user had control over. On the other hand, while hard-coding an API token into the source of a client is dangerous practice when the client runs on a team server or a user's desktop, it is completely unacceptable for a JavaScript client. Sending the API client software to the user's browser distributes the token used far and wide. Publishing the API token for a user that is a Viewer on a project (has read-only access) is the security equivalent of making the project public. Distributing the token for a collaborator could allow anyone anywhere to do anything within the project that they want.

It is almost as dangerous to have the user API token to be used transmitted from the server to the client outside of the client source. Even if the transmission of the token is secure, anyone with access to the browser could obtain the token value and then re-use it again later in a different application.

We also strongly discourage the creation of sample JavaScript client code which is intended to have the user API token added to it by the person running the code. This kind of sample can easily be incorporated into larger, more widely distributed pieces of code without the dangerous handling of the user API tokens being corrected.

Note that Tracker's current configuration prevents the browser from sending authentication parameters along with CORS requests. This doesn't affect sending of the API token (either in the header or as a query parameter), but it does prohibit the browser from sending credentials through Basic auth, which has the effect of prohibiting a client that relies on CORS from using the /me endpoint to obtain a user's API token.

CORS will be limited to OAuth-using clients. At some point in the future, we will be adding OAuth support to version 5 of the Pivotal Tracker API as an alternative to using API tokens for user authentication. Once this occurs, we will be restricting the availability of CORS to OAuth-using clients and prohibiting the use of API tokens for authenticating CORS-using requests without increasing the API version number. There will of course be an announcement prior to the removal of support for tokens+CORS, but it is recommended that CORS only be used in clients that have relatively short deployment cycles so that a client version updated with OAuth support can be released between the incorporation of OAuth support and the removal of tokens+CORS support, in order to avoid an interruption in client access to the Tracker API.

Request Authorization

Each request made to the Pivotal Tracker API is authorized based on the result of authentication. If the authentication credentials provided are invalid, the request will fail prior to authorization. If no authentication credentials are provided, authorization is based on whether the information requested is available to an anonymous visitor. Otherwise, authorization is based on the authenticated Tracker user's relationship to the resource(s) referenced by the request.

Each Tracker user profile can be associated with any number of projects and any number of accounts. Whether a particular request can the authorized depends on which resources the request references, and the user's relationship to those resources through the user's project(s) and account(s). API requests conform to the same authorization rules implemented by the Tracker web site. In general:

  • A person who is a Viewer on a project can make GET requests to fetch resources in that project, but cannot make requests that would modify it.
  • Only a person who is an Owner of a project can modify the project settings or modify its integrations.
  • An anonymous request with no authentication can only be a GET, and then only for resources in a public project. That is, a project which has its public attribute equal to true.

In addition to the general rules that apply broadly to all resources that are contained in a project, there are some less-obvious constraints that apply to specific resources and attributes:

  • The email attribute of person resources is filtered out of responses, even when the client has requested it to be included, based on specific authorization criteria, independent of whether the underlying request is authorized. There are a number of detailed rules regarding when the email attribute will be populated, but in short it will only be available to requests made using authentication for a user that would be able to view the email address through the Pivotal Tracker web site. A client application designed to work on behalf of arbitrary users must accommodate not receiving email in the responses it receives.
  • A POST request to the /projects endpoint will be authorized only for users who have the "project creator" permission on the account whose ID is included in the request parameters.

API Versioning

The current stable version of the Pivotal Tracker API is "v5".

This section lays out general policies on ensuring a client's ongoing compatibility with the Tracker API in general, and with the current recommended API version (v5) in particular. We will be adding new capabilities to the API without increasing the version number while those capabilities are compatible with the existing functionality and encodings. We are also introducing a standing edge API version that will be used to introduce and obtain feedback on new features that are incompatible with the current API version, that we intend to incorporate in the next stable API version. The determination of what API behaviors are incompatible and therefore have to be introduced in edge or a new numbered version is made based on the content of this section. The use of edge and what it currently contains is covered in its own reference page, see REST API Edge.

Meaning of "Stability" in the API Specification

The behavior of API v5 will be held stable until and unless it is announced to be deprecated, and it will remain supported for several months after such time.

However, for the purposes of the Tracker API, "stable" means that no published endpoint, resource, or attribute will be removed or redefined. We intend to continue adding endpoints, resources, and attributes to existing resources, incrementally expanding the definition of v5 in forward compatible ways. The balance of this section lists potential variations in the responses generated or requests accepted by the API, which ones would be considered an incompatible change in the API, and which ones an API client must be written to anticipate and accommodate.

Transport and Encoding

An API client should be tolerant of changes in the way the HTTP protocol is used by the Tracker server, as long as transport remains compliant with the standard. In particular:

  • Changes in the types of transport encodings available
  • Changes in the content of cache-control headers returned by Tracker
  • Introduction of new extension headers in HTTP responses
  • Accepting and operating on HTTP headers in requests that begin with X-Tracker

An API client should be tolerant of changes in the formatting of responses from the API, as long as responses remain compliant to the JSON standard. In particular, the following can all vary within a JSON-encoded response without having any impact on the data structure it is intended to convey, and therefore without constituting a change to the definition of the API:

  • The amount and location of white-space that is outside of a string constant or symbol
  • The order of keys within the text representation of a hash
  • The types of quotation marks used
  • The escaping of characters within a string constant
  • Variations in the inclusion of optional characters in value encodings, for example including or excluding ".0" at the end of floating-point constants

Authors of v5 API clients are strongly advised to use an available, "full" JSON parser for their client rather than building something custom.

Data and Operations

Expansion of available data.

Future API rules that limit the range of a value.

Restrictions on writeability may be relaxed.

Addition of new resource types implies the possibility for new values to occur in some fields. For example, The primary_resources and changes attributes of activity structures are untyped arrays of object hashes. Each of these may reference any of the resources available in the API, including ones not originally defined when the API was released. This means that clients should be prepared to discover hashes which identify themselves as matching resource types that it isn't aware of.

Cases to Be Cautious Of
  • ISO 8601-encoded datetime values. The current server-side implementation returns most datetime values most of the time in the UTC time zone, denoted with the encoded time string ending in "Z". However, clients should be prepared to parse any legal ISO 8601 date/time value, including ones that are returned in a time zone other than UTC.
  • The point_scale attribute of project is a string, not an enumeration. The client should be prepared to handle any comma-delimited list of decimal-encoded floating-point numbers as a point_scale. In addition, the set of point scales that are "built in" to Tracker may change at any time. The client should rely on the value of the point_scale_is_custom flag if it needs to distinguish built-in versus user-provided point scales, and not have its own list of what the built-in point scale values are.
  • The estimate attribute of story. Note that the type of this attribute is float, even though currently only whole numbers are ever used (because Tracker doesn't currently permit point-scale strings to be specified with fractional values). estimate is explicitly defined as a float to permit future support of fractional values in a project's point scale. Clients will be expected to handle this when it occurs.
  • The message string in activity is an English string intended to be user-presentable, which summarizes the action that the activity represents. There are no guarantees as to the format or content of this string over time, except that the content of the highlight from the same activity record will be a substring of message. If you want to extract information from an activity, you should parse the object structure and not the content of the message string.
  • Note that the kind of activity structures is special, and rather than being the fixed string "activity", it is an "open enumeration" of strings that end in ..._activity. Because the initial portion of each activity's kind is a string made up of a resource name and a type of operation performed on that resource, it is possible for clients to encounter activity structures that reference operations and/or resources of which it is not aware. For purposes of displaying activity to users, the client may ignore such structures, or display the message string from them, or attempt to build a message based on the portions of the structure that it does recognize. For purposes of updating the client's local copy of project data (if any), it should attempt to process the changes array of all activity structures received, as it is very likely it will find changes to resources it does recognize even in a type of activity that it doesn't recognize.
Interpreting Maturity Notes

The reference documentation for the API endpoints (URLs and request methods), and the underlying resource data structures manipulated by the API, includes a small fraction of items which are marked unimplemented, experimental, beta, or deprecated. These are warnings about the degree of support and stability that the marked items (entire endpoints/resources, or individual parameters or attributes). Items without one of these maturity indicators are "normal": they're fully supported, expected to behave reliably, and intended to be stable through the entire lifespan of the API version that includes them. Note endpoints/resources marked as experimental or beta may change and/or have different rate limits than "normal" endpoints.

Response Customization

Default Response Content

Inclusion of Attributes

When making a request, the client can explicitly determine which attributes of each resource should be returned by sending the fields parameter. This parameter and its effects are described in Selecting Attributes to Include in Responses.

There are some attributes that are always included in the representation of a resource when a response is generated, regardless of the fields parameter. In the Resources section, the description of one of these attributes will include the sentence This field is always returned. For other attributes, most are included by default when fields isn't used. Some, however, are defined and available, but only included in a response when specified with fields. The description of an attribute that is not put into responses by default will include the sentence This field is excluded by default.

Also regardless of the fields parameter, scalar attributes whose values are "empty" (missing, undefined) are not included in the response. For example, if the following were returned from a request with fields=name,description

[ { "id": 10, "name": "Establish ice-surface transport landing area", "description": "We have to ensure that we've got enough cleared and smoothed ice to land all our transports" }, { "id": 12, "name": "Reassemble main power generator for base" } ]

it would indicate that the second story's description is empty. This does not apply to attributes whose values are arrays: empty arrays and nested structures are always explicitly included unless the field is omitted due to the fields parameter. For example, the following might be returned by a GET of /projects?fields=label_ids,story_ids,epic_ids

[ { "id": 5, "label_ids": [1992, 2011], "story_ids": [42, 973, 55, 1414, 1415, 1418, 828], "epic_ids": [] }, { "id": 7, "label_ids": [], "story_ids": [], "epic_ids": [] }, { "id": 15, "label_ids": [749, 1141, 1238], "story_ids": [], "epic_ids": [3, 5, 9] } ]

In this example, the authenticated user is associated with three projects. The first project contains several stories and labels, but no epics. The second project is essentially empty, and the third project may be in the planning phase—several epics but no stories yet.

Note that empty/missing is defined as null for all scalar resource attribute types. This is unimportant for interpreting resources in responses (where they won't be included), but for operations used to change an attribute from some other value to empty must include the attribute name with a value of null. When a new resource object is created by an API operation (a POST to a resource endpoint) the response contains the complete (non-null) content of the object, including any default values supplied by the server.

Representation of Associations Between Resources

Many of the attributes of resources are simple values as described above. Others represent the relationship between resources. For example, the epic resource contains several attributes that express relationships: project_id, label_id, after_id, before_id, and comment_ids.

Of these, project_id is a reference to a resource that contains the epic, and potentially other epics as well. before_id and after_id are also special, because they are associations to other instances of the same type of resource. These are always returned as numeric IDs (including nested copies could lead to a request that should return a single epic actually returning multiple copies of every epic in the project).

For the other association attributes, they may be included in responses (or provided as parameters) either as an integer ID or as a nested structure containing attributes of the associated type of resource. Whether an association attribute is an integer (or array of integers) or it is a structure (or array of structures) is determined by the attribute name that is used. Most resource attributes whose name ends in "_id" or "_ids" implies the presence of an alternative, structure-containing attribute that has the same base name but without the _id suffix. (There are a few exceptions, attributes which are strings that contain IDs used by external systems with which Tracker integrates. These do not behave like association attributes, and are marked in the individual resource descriptions.)

Continuing to use epic as an example, the implied attribute names for complete resources are: label, project and comments. Note that comments is plural, because comment_ids attribute is plural.

Response-Controlling Parameters

This section describes standard parameters that the client can include as part of any request. These parameters are listed/described here only. They apply to all API requests, but for clarity their definition is not repeated again in the description of each individual request.

Each successful request causes the server to generate a response containing either a single data structure or an array of structures. The schema for these structures (the type of object they encode) is indicated in the description of each request. Each structure is a composition of scalar values, arrays, and/or nested sub-structures. Each possible response data structure has a default composition, the particular elements that will be included in the response if the requesting client doesn't include any parameters to affect the response format.

In general, the default is to:

  • exclude all scalar values that do not have a defined value,
  • include all other scalar values that can occur in a structure,
  • exclude all association collections (associations whose values are arrays),
  • return any collections that are included as arrays of nested resource hashes rather than IDs, and
  • return scalar associations as integer IDs.

Responses from individual API requests can differ from this general case, intended to make responses to request without configuration parameters fit the most common use cases. Each is noted in the description of the particular field(s) in the Resources section.

Selecting Attributes to Include in Responses

Control of what fields are included in responses, and whether an association's content is expressed as a resource ID (or number) or as a nested resource hash, is accomplished through the fields parameter. This query parameter is accepted by almost every API request, and its value controls the content of the response. However, it cannot be used to control responses in CSV format. When "the default" of what is included in a response is discussed, this is the API's behavior when no fields parameter is sent by the client.

There are several different aspects of response generation that can be affected by the fields parameter. Each one of the following subsections addresses one of these aspects, starting from the simplest use of fields and continuing through how all the different types of behavior interact.

fields for Single Resources

If the fields parameter is sent with a request, only those resource attributes whose names are specified by the comma-separated list that is the value fields parameter will be included in the response.

For example, without fields the request GET /services/v5/projects/1/stories/42/tasks could produce the following reply:

[ { "id": 10, "kind": "task", "story_id": 42, "description": "Refuel X-wing fighters", "complete": false, "position": 1, "created_at": "2013-03-19T05:51:00Z", "updated_at": "2013-03-19T05:51:00Z" }, { "id": 12, "kind": "task", "story_id": 42, "description": "Run diagnostics on all flightline R2 droids", "complete": false, "position": 2, "created_at": "2013-03-19T05:51:25Z", "updated_at": "2013-03-19T05:51:25Z" } ]

If the request had instead been executed using the fields parameter, like GET /services/v5/projects/1/stories/42/tasks?fields=description,complete, then the response would have been:

[ { "id": 10, "description": "Refuel X-wing fighters", "complete": false }, { "id": 12, "description": "Run diagnostics on all flightline R2 droids", "complete": false } ]

Note that even though it was not included in the fields list, the id element is included in the response structure for each task. This is because some elements, like id, are included in responses regardless of the content of the fields parameter. These "always returned" fields are indicated in the description of each attribute in the Resources section.

fields for Controlling Associations

Just as scalar resource attribute names can be listed in the value of the fields parameter to select what should be included in server responses, including the names of associations will cause them to be included in the response.

For example, the request GET /services/v5/projects/1/stories/42 could produce the following reply:

{ "id": 42, "kind": "story", "project_id": 1, "name": "Prepare fighter wing to assault Death Star", "description": "Get Red, Gold, and Blue squadrons prepped to fly ASAP. Includes getting the new astromech droids from Commenor integrated.", "story_type": "feature", "current_state": "unstarted", "estimate": 2, "requested_by_id": 421, "labels": [], "url": "https://www.pivotaltracker.com/projects/1/stories/42", "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }

This example reflects the default set of response fields for the story resource. Several of the story attributes that are not excluded by default are not present in this example, illustrating the fact that "empty" attributes are omitted from responses regardless of whether they're ordinarily included by default or have been explicitly requested through the fields parameter. With this same data, the request GET /services/v5/projects/1/stories/42?fields=name,description,story_type,requested_by,owned_by,label_ids,comments,tasks could produce the following reply:

{ "id": 42, "name": "Prepare fighter wing to assault Death Star", "description": "Get Red, Gold, and Blue squadrons prepped to fly ASAP. Includes getting the new astromech droids from Commenor integrated.", "story_type": "feature", "requested_by": { "id": 421, "kind": "person", "name": "General Jan Dodonna", "initials": "JD", "email": "dodonna@yavin4.alliance-secure" }, "label_ids": [], "comments": [], "tasks": [ { "id": 10, "kind": "task", "story_id": 42, "description": "Refuel X-wing fighters", "complete": false, "position": 1, "created_at": "2013-03-19T05:51:00Z", "updated_at": "2013-03-19T05:51:00Z" }, { "id": 12, "kind": "task", "story_id": 42, "description": "Run diagnostics on all flightline R2 droids", "complete": false, "position": 2, "created_at": "2013-03-19T05:51:25Z", "updated_at": "2013-03-19T05:51:25Z" } ] }

There are several things to note in this example:

  • Only the scalar fields included in the fields parameter's list are in the response. Others that were included in the default response are omitted.
  • requested_by has replaced the default requested_by_id, and now contains a nested resource structure containing the information for the person.
  • owned_by is not present, just as owned_by_id wasn't present in the original example because (presumably) the story has no owner. Because owned_by is a scalar association, it is omitted when empty, whether the requested expression of the association is an ID or a nested structure.
  • label_ids has replaced the default labels because it was explicitly requested, and is still present in the result because it is a collection association, and array-valued fields are always included, even when empty.
  • The nested person and task structures follow their own specific defaults. So, specifically, they all contain the kind field, even though kind was not included in the fields list and there is no kind field in the top-level story structure.
fields Starting From the :default

In cases where the client wants to fetch most or all of the attributes of a resource plus one or more of the available attributes that are not included in responses by default, the value strings of the fields parameter can become quite long. To help address this, the string :default can be included in a fields parameter as if it were an attribute name, and it instructs the server to include all of the resource attributes in the current response that would normally be included by default. For example, the request GET /services/v5/projects/1/epics/5 could produce this response:

{ "kind": "epic", "id": 5, "project_id": 99, "name": "Build-out of Emperor's Suite", "label": { "kind": "label", "id": 19, "project_id": 99, "name": "suite", "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }, "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }

To obtain this same response but also include the list of the epic's comment IDs, the following could be used as the value for the fields parameter:

  kind,project_id,name,label,created_at,updated_at,comment_ids

This is relatively long, and can be much longer for resources with more attributes. Using the :default short-hand, it can be replaced by

  :default,comment_ids

So that the request GET /services/v5/projects/1/epics/5?fields=:default,comment_ids could produce this response:

{ "kind": "epic", "id": 5, "project_id": 99, "name": "Build-out of Emperor's Suite", "comment_ids": [ 23, 28, 34, 67, 72, 97, 100, 104, 113, 126, 129, 137 ], "label": { "kind": "label", "id": 19, "project_id": 99, "name": "suite", "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }, "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }

It is possible to specify an attribute name along with :default that over-rides a default attribute. For example, by default the label_id attribute of an epic is included in results as label with a nested structure representing the label resource's content. To cause epics to be returned by the API with the label as only an ID, the fields value of :default,label_id could be used. A request with that value for fields could produce the following response:

{ "kind": "epic", "id": 5, "project_id": 99, "name": "Build-out of Emperor's Suite", "label_id": 19, "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }
fields for Selecting Content of Nested Resources

An association (collection or not) will be filled in with a nested structure when its non-_id name is in the fields list. If only the association's name is present, then the nested structure will conform to that particular resource type's response defaults, as shown in the example above. The fields included in a nested structure can be controlled by following the association field's name with a pair of parentheses surrounding the field list for the nested structure. The general form of this kind of value to the fields parameter is:

  scalar,scalar,association(subfield,subfield),scalar

In this example, the "scalar" and "association" entries control the population of the top-level resource type being returned by the request. The "subfield" entries are the names of attributes (scalar or association) of the type of resource referenced by the top-level resource's association. Any association name can be followed by a parenthesized list of sub-resource field names, even if it is already inside the parenthesized list for another association.

More concretely, the request

GET /services/v5/projects/1/epics/4?fields=name,description,label(name),comments(text,file_attachments,google_attachments(title))

could produce the following reply:

{ "id": 4, "name": "Operation Skyhook", "description": "Obtain plans for and intelligence about the Empire's new weapon station.", "label": { "id": 24, "name": "skyhook" }, "comments": [ { "id": 1433, "text": "Here's the list of our current Bothan contacts. Eye's Only.", "file_attachments": [], "google_attachments": [{ "id": 93, "title": "Bothan Humint Latest" }] }, { "id": 4242, "text": "Death Star plans in hand! See attached.", "file_attachments": [{ "id": 126, "kind": "file_attachment", "filename": "station.vrml", "size": 67832388065637, "width": 4096, "height": 4096, "uploader_id": 88, "thumbnail_url": "https://s3.amazonaws.com/prod.tracker2/resource/1622777/thumb.gif?AWSAccessKeyId=AKIAIKWOAN6H4H3QMJ6Q&Expires=1363677792&Signature=Q6BZTAUtJXsiCQ9R%2BkcHoL%2BfSUk%3D", "thumbnailable": true, "uploaded": true, "download_url": "https://www.pivotaltracker.com/resource/download/1622777", "big_url": "https://s3.amazonaws.com/prod.tracker2/resource/1622777/bigthumb.gif?AWSAccessKeyId=AKIAIKWOAN6H4H3QMJ6Q&Expires=1363677792&Signature=Q6BZTAUtJXsiCQ9R%2BkcHoL%2BfSUk%3D", "created_at": "2013-03-19T06:56:19Z", "content_type": "model/vrml7" }], "google_attachments": [] } ] }

The :default short-hand can be used at any level of the attribute selection within nested resources. Also, it doesn't matter what position within a comma-separated list of attribute names the request

GET /services/v5/projects/1/epics/4?fields=name,description,comments(file_attachment_ids,:default)

could produce the following reply:

{ "id": 4, "name": "Operation Skyhook", "description": "Obtain plans for and intelligence about the Empire's new weapon station.", "comments": [ { "kind": "comment", "id": 1433, "epic_id": 4, "text": "Here's the list of our current Bothan contacts. Eye's Only.", "file_attachment_ids": [], "person_id": 106, "created_at": "2013-03-19T05:30:13Z", "updated_at": "2013-03-19T05:30:13Z" }, { "kind": "comment", "id": 4242, "epic_id": 4, "text": "Death Star plans in hand! See attached.", "file_attachment_ids": [126], "person_id": 106, "created_at": "2013-03-23T14:41:38Z", "updated_at": "2013-03-23T14:41:38Z" } ] }
Selecting the Date/Time Format

Several fields of different resources contain date/time values. These can be encoded either in a string formatted according to the ISO 8601 standard (Representation of dates and times) or as an integer count of milliseconds elapsed since the epoch, as is commonly used by the JavaScript Date object (as in new Date(value)).

When sending date/time values to Tracker in an API request, the client can supply them in either format. Tracker will determine which is being presented by examining the value, and interpret it accordingly. This parameter will work with responses in the CSV format.

Response date/time values are in the ISO 8601 by default. If the client wants to receive date/times in milliseconds, the following parameter should be added to the request's query string:

  ?date_format=millis

The date_format parameter applies to the entirety of a response—there is no way to request that some date fields be formatted in milliseconds and others as ISO 8601.

Paginating List Responses

Many of the API's GET requests that return an array of resource structures support pagination, so that the request can be repeated multiple times with all but the pagination parameters the same, and obtain a different subset of the overall available result set for each response. When a request supports pagination of the results, it is noted in the description of the operation below. The Envelope Return Structure section describes the pagination structure that is returned with each result from a paginated endpoint, and which contains information about the subset of the possible results that is contained in the current response. It is possible to use these parameters when requesting a response in CSV format.

The two request query parameters that control pagination are:

  • offset — An integer, that gives the number of items between the first item returned by default (no offset provided) and the first item that is desired in the response to the current request. If no offset parameter is supplied for a page-able request, the default offset used will be 0. Most page-able requests return the first available item with offset=0. However, there are a couple of endpoints where the URL used already implies a subset of a larger possible result space. In this case, negative offset values are acceptable to specify that the results should include items that come before the default first item.
  • limit — An integer, that gives the maximum number of items to include in the response to the current request. If no limit parameter is supplied for a paginated request, a default limit value will be used. If the limit parameter value supplied is larger than the maximum limit currently available through the endpoint, the limit used is capped at the maximum, and the value used, rather than the value from the request, will be reflected in the pagination.limit (or X-Tracker-Pagination-Limit) value in the response.
    Each paginated operation has its own independent default and maximum limit values, which will change from time to time, as the result of server updates or dynamic conditions.

The pagination structure included in the response from a page-able request includes the information necessary for the client to generate requests for the next or previous group of items, and to know how many items make up the total possible data set.

For example, the request:

  GET projects/1/stories?offset=25&limit=300&envelope=true

Could generate the response:

{ "http_status": "200", "project_version": 42, "pagination": { "total": 1543, "limit": 300, "offset": 25, "returned": 298 }, "data": [] }

The offset value in the response will always be either the parameter value from the request or the default provided by the server. If there was no limit parameter provided in the request, then the limit value in the response will be the default limit currently available for the request operation performed. If a limit parameter was included in the request, then the limit value in the response will be the minimum of it and the maximum allowed limit.

The total value in the response is the number of available item positions at the current time that correspond to the other (the non-pagination) request parameters provided. Depending on the endpoint, it is completely possible for the value of the total to change between subsequent requests, even those made very close together in time. The value of returned in the response gives the number of entries in the data array within the response envelope (stories in the example above).

There are several reasons why the value of returned may be smaller than that of limit. First, if the offset is large enough that there are fewer result items remaining than the limit, then returned will reflect the actual number of items in the response. For example, assume that the total in a response is the same as shown above, 1543, and that the request's offset was 1500 and the limit was 150. In that circumstance, returned could be 43.

There is a second reason that returned might be less than limit (or less than total in the case where total is also less than limit). Occasionally, due to the way Tracker indexes information, there will be item locations counted in total that do not actually contain items. For example, with a total of 1543, and the request's offset equal to 100 and limit equal to 150, it is reasonable to expect that returned would be equal to 150. And most of the time it will be. But occasionally it might be 149 or 137. If this case occurs, the client should still add the current offset and limit values in order to generate the offset parameter for the next request if the desired behavior is to obtain successive non-overlapping pages of data.

Third, it is possible for the underlying data that is being requested to change between one request and another using pagination to request a different portion of the same results. At a minimum, clients should read the values of total and limit provided in each response and not simply assume that the values returned in the first request of a set remain fixed. Depending on the client's requirements for precision in data use/presentation, it may not need to do any other processing. However, if the client has to be guaranteed to access all of the data items that match the request, it may need to be capable of re-fetching some pages or overlapping pages fetched in order to detect/compensate for data changes.

CSV Response Format

Some API GET responses may be returned with CSV encoding. All responses in this format are the structured the same way, with headers setting the content type and disposition, and comma separated text values in the body. Datetimes are returned in user's local time zone in the MM/DD/YYYY HH:MM:SS format. Alternative date/time formats, the envelope option and field selection are not supported.

Example of an API response with CSV encoding:

Headers

  Content-Disposition: attachment; filename="story_transitions.csv"
  Content-Type: text/csv

Body

  Story ID,State,Occurred at (MST),Project ID,Performed By ID,Project Version
  551,unscheduled,12/1/2015 12:00:00,99,100,4
  552,unscheduled,12/1/2015 12:00:00,99,100,5
  553,accepted,12/1/2015 12:00:00,99,101,6

Activity Information

The Tracker API can provide information about the sequence of changes made to the content of projects by returning data formatted as activity structures. There are API endpoints to retrieve recent activity within a particular scope, and Pivotal Tracker can POST JSON activity structures to the Internet URL of your choice using web hooks.

The Activity Endpoints

There are four different Activity endpoints. Their parameters, pagination, and responses (arrays of activity resources) are all common. The difference between them is that each implicitly filters the activity selected for the response by different criteria.

Information from all of these endpoints is returned in reverse chronologic order, so that the first activity in a response indicates the most recently made modification to the project, and increasing values of the offset pagination parameter select older and older portions of the project's history. Clients using this endpoint should take into account that other clients, including users working in the Pivotal Tracker web application, can be continuously making changes that cause new activity to be added to the beginning of the responses from this endpoint. Not only should it be expected that, over time, the responses to identical activity requests will change, but the a client which is paging through multiple activity requests and wants to guarantee that it processes all activity exactly once should pay attention to the project version numbers of activity it has already handled, as the same activity structures can occur at the end of one request and at the beginning of a subsequent request for the next page of activity if updates have been made to the data on the server between those the requests.

Project Activity
The endpoint /projects/{project_id}/activity returns all of the activity (over the selected period) that affected the project whose ID is included in the request path, regardless of who took the actions that caused the activity or what resources within the project were modified. This endpoint returns information equivalent to the "Project History" panel in Pivotal Tracker's native web UI.
Story Activity
The endpoint /projects/{project_id}/stories/{story_id}/activity returns all of the activity (over the selected period) that affected the single story identified by the request path. This will always be a subset of the activity for the story's project over the same period, and is equivalent to the content of a story-specific History panel in Pivotal Tracker.
Epic Activity
The endpoint /projects/{project_id}/epics/{epic_id}/activity returns all of the activity (over the selected period) that affected the single epic identified by the request path. This will always be a subset of the activity for the story's project over the same period, and is equivalent to the content of a story-specific History panel in Pivotal Tracker. Note that the activities returned by this endpoint are ones that affected the epic directly. This endpoint does not return the collective activity for all of the stories contained in an epic.
User Activity
The endpoint /my/activity returns all of the activity (over the selected period) that was instigated by the user whose authentication credentials were used to make the request. Unlike the three other activity endpoints, this endpoint can return activity from a variety of projects. In this case, activity structures continue to be returned in reverse chronologic order over all, regardless of the relative version number sequences in the different projects.

See the references for these endpoints for examples of activity items.

Interpreting an Activity Item

activity structures are intended to fulfill two primary purposes:

  • allowing clients to display messages to the user describing changes that have taken place within the project, and
  • allowing clients to update any local copies of data previously obtained from the API, to ensure the client and server are in sync.

The following example shows a request that retrieves from the /my/activity endpoint. The response shown is a representative set of activity items representing a handful of common operations. (Note that this example uses pagination to omit one of the available result items, and the envelope query parameter to have the response formatted with the pagination information included in the response body instead of HTTP response headers.)

export TOKEN='your Pivotal Tracker API token'

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/my/activity?envelope=true&limit=3"

Hide Response
Headers
Response Body
{"data":[{"kind":"story_move_activity","guid":"99_65","project_version":65,"message":"Darth Vader moved 2 stories","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":562,"original_values":{"before_id":551},"new_values":{"before_id":557},"name":"Garbage smashers on the detention level have malfunctioned","story_type":"bug","story_priority":"p3"},{"kind":"story","change_type":"update","id":564,"original_values":{"after_id":557},"new_values":{"after_id":565},"name":"Evacuate, in our moment of triumph","story_type":"feature","story_priority":"p3"}],"primary_resources":[{"kind":"story","id":564,"name":"Evacuate, in our moment of triumph","story_type":"feature","url":"http://localhost/story/show/564","story_priority":"p3"},{"kind":"story","id":562,"name":"Garbage smashers on the detention level have malfunctioned","story_type":"bug","url":"http://localhost/story/show/562","story_priority":"p3"}],"secondary_resources":[],"project":{"kind":"project","id":99,"name":"Death Star"},"performed_by":{"kind":"person","id":101,"name":"Darth Vader","initials":"DV"},"occurred_at":"2024-03-12T12:00:00Z"},{"kind":"story_move_activity","guid":"99_64","project_version":64,"message":"Darth Vader moved 2 stories","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":562,"original_values":{"before_id":557},"new_values":{"before_id":551},"name":"Garbage smashers on the detention level have malfunctioned","story_type":"bug","story_priority":"p3"},{"kind":"story","change_type":"update","id":564,"original_values":{"after_id":565},"new_values":{"after_id":557},"name":"Evacuate, in our moment of triumph","story_type":"feature","story_priority":"p3"}],"primary_resources":[{"kind":"story","id":564,"name":"Evacuate, in our moment of triumph","story_type":"feature","url":"http://localhost/story/show/564","story_priority":"p3"},{"kind":"story","id":562,"name":"Garbage smashers on the detention level have malfunctioned","story_type":"bug","url":"http://localhost/story/show/562","story_priority":"p3"}],"secondary_resources":[],"project":{"kind":"project","id":99,"name":"Death Star"},"performed_by":{"kind":"person","id":101,"name":"Darth Vader","initials":"DV"},"occurred_at":"2024-03-12T12:00:00Z"},{"kind":"comment_create_activity","guid":"99_62","project_version":62,"message":"Darth Vader added comment: \"I want them alive!\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":112,"new_values":{"id":112,"story_id":555,"text":"I want them alive!","person_id":101,"created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z","file_attachment_ids":[],"google_attachment_ids":[],"attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":555,"original_values":{"follower_ids":[]},"new_values":{"follower_ids":[101]},"name":"Bring me the passengers","story_type":"feature","story_priority":"p3"}],"primary_resources":[{"kind":"story","id":555,"name":"Bring me the passengers","story_type":"feature","url":"http://localhost/story/show/555","story_priority":"p3"}],"secondary_resources":[],"project":{"kind":"project","id":99,"name":"Death Star"},"performed_by":{"kind":"person","id":101,"name":"Darth Vader","initials":"DV"},"occurred_at":"2024-03-12T12:00:00Z"}],"http_status":"200","pagination":{"total":12,"limit":3,"offset":0,"returned":3}}
Default Message and Metadata

Each activity structure contains an English-language message string composed by the server which is intended to be displayable to the user. Clients may choose to simply present this string to the user as-is. (Note that it should only ever be presented to the user. The actual content of the messages we generate for the same types of activity may change over time, and parsing the message string to obtain data about the activity is strongly discouraged.)

If the client wishes, it can use the highlight string as a guide to how the message may be formatted. The value of highlight will always be a substring of the message and can be used to divide the message into three parts (before the highlight, the highlight itself, and after). Many messages correspond to a user-verb-object structure, with the verb being contained in the highlight. The Pivotal Tracker web interface displays the highlight in an empasis font, relative to the rest of the message, and whether highlight identifies the verb in the message or not, it will correspond to the portion of the message that we believe deserves accenting relative to the rest of it.

The other consideration when using the default message is that it may be phrased to assume the context in which it is displayed. For example, some messages use indefinite phrases as objects, such as "this feature" for an activity that describes a change to a feature-type story. The resource(s) that any object phrase references are those included in the primary_resources array (more on this attribute below). It is recommended that a client using the default message display identifying information from the primary_resources proximal to the message.

The time at which the operation which is represented by an activity item was performed is in the occurred_at attribute of an activity.

The primary_resources array contains a list of the identifying information for the resource(s) that were updated by the operation. It is an array because activity can result from user operations that are performed on a number of resources simultaneously. For example, when multiple stories are moved (reprioritized) as a group, one hash occurs in primary_resources for each story affected. In addition to identifying the context that might be referenced by the default message string, primary_resources can be used for grouping. In the "Project History" panel in Pivotal Tracker, operations performed on the same "thing" at around the same time are displayed under a banner identifying the "thing" and with sub-items organized chronologically instead of in reverse (the overall organization of the different History panels). The primary_resources array is what is used to identify the "thing" (resource or group of resources) that have been acted on for grouping purposes.

What resource(s) appear in primary_resources depend on both the type of activity and the kind of resources. In particular, resources that are in the object hierarchy below stories and epics (comments, tasks, followers, etc.) do not appear in primary_resources. Instead, the story or epic that they belong to will be listed. Most other activity lists the same type of resources in the primary_resources array as are named in the activity's kind, but resources that are children of project may have the project that contains them as the entry in primary_resources.

A hash in the primary_resources array will have the following keys:

  • kind — This reflects the kind attribute of the resource that is being referenced by this primary_resources entry.
  • id or number — This is the unique identifier for the particular resource being referenced. As always, iteration and iteration_override have number while other persistent resources have id.
  • An identifying string — In most cases there is a string-type value in the hash in primary_resources. The content of this key will be ellipsified if the matching attribute from the referenced resource is too long. The key for this string will match the "main" string attribute in the type of resource being represented. For example, the key is name for stories, epics, projects and other resources with a name attribute. The key is description for tasks and text for comments. The resources for which no identifying string is available include things like iteration and iteration_override.
  • Sometimes story_type — when the resource is a story, its hash in primary_resources will contain this additional key.
  • Sometimes url — when the resource is a story or an epic, its hash in primary_resources will contain this additional key.

Note that the "identifying information" values in primary_resources reflect current information. For example, if a comment had been added to a story five months ago, fetching the activity for that occurrence immediately afterward would give you the story name that was present at that time. But if the story's name had been edited a month ago, and now you fetch the activity for that same change, the response will contain the story's current name, and not the value that the story name had at the time the comment was added. This is intended to ensure that users looking at activity see reference information that matches what they'll see elsewhere in Tracker at the moment.

Creating a Message

A client might want to make its own message string rather than use the default message provided in the activity. Perhaps you want to eliminate the indefinite references in some Tracker messages ("this story"), or want to create a message with language localization. The activity contains all of the information necessary to create the default message strings and the context in which they are displayed in the various History panels in the Tracker app.

The phrasing of messages, choice of words (like the verb in the default message), etc. will usually require the kind of the activity to be parsed by the client. While new words may be introduced as new activity structures are added to the API over time, the existing vocabulary of activity kinds is a stable part of the API specification. Thus, the client can rely on story_update_activity to always be identified in the same way, so that it can create a stable mapping to the fixed language in its user representation of activity.

As mentioned above, the information needed to identify and describe to the user the resource(s) that were affected by the action being described is present in the primary_resources array.

In addition, accurately describing an activity to the user frequently requires information about resources other than just the "primary". For example, if the activity represents a user's clicking the check box on a story's task to mark it completed, the primary_resources array will contain the identifying information about the story. However, the information identifying the task that was modified is included in the changes array along with the information about exactly what was modified. The exact structure is in History Details, next.

Similarly, you may need to examine the exact resource attributes updated in order to make very specific messages. For example, a story_update_activity that represents a user changing just a story's state from "started" to "finished" contains a message that says the user "finished" the story, rather than a more generic sentence about editing or updating the story. To generate messages this specific, you'll have to look at the presence (and absence) of specific attributes in a particular resource's entry in the changes array.

History Details

When Pivotal Tracker displays information in one of its History panels, it not only includes the message strings from activity, and groups multiple activities together based on matching primary_resources, but it also includes an expandable "details" section for some kinds of history entries. The information in these sections comes from the changes array in activity.

The changes array contains one or more hashes, each of which contains identifying information about the resource, a list of all the changed attributes' new values, and a list of those attributes' values from immediately before the change. For example (for reference, a matching default message string is "Darth Vader started this feature"):

{ "changes": [{ "kind": "story", "id": 556, "name": "Interrogate Leia Organa", "story_type": "feature", "change_type": "update", "original_values": { "after_id": 557, "before_id": 555, "current_state": "unscheduled" }, "new_values": { "after_id": 559, "before_id": 566, "current_state": "started" } }] }

kind, id (or number), name (identifying string), and story_type are all present and populated identically to the matching hash entries present in a primary_resources hash, described above. (url is not included in the identifying information in the changes array.)

change_type identifies how the resource has been modified. The possible values for this string are create, update, and delete. The set of possible values for change_type may be extended without a change in the Tracker API version number. Clients should be prepared to receive a hash that contains a change_type value that they do not know how to process, and should gracefully ignore such hashes.

new_values is a hash. The keys of the hash are a subset of the attributes defined for the type of resource identified by the changes entry's kind. In this example, the single entry in changes represents a change to a story, so the keys that can occur in the new_values hash are those that are defined for a story, the types of the values of each key conform to the story resource's schema, etc. There will not be a new_values entry in the case where change_type is "delete".

The values in new_values will remain invariant over time. Unlike the resource-identifying information in an activity in which current resource information is returned, these values are always the values that the activity actually produced, even though many of the fields modified may have been re-modified subsequently.

Note that, in some cases, multiple keys that are mutually exclusive in other contexts where the resource's values might occur or be returned will be included in new_values. For example, when the labels on a story have been updated, both a labels key and a label_ids key will be present. Unlike the other cases in which a resource association may be represented by a non-_id attribute name (where the content of the attribute would be a complete nested resource), this key naming convention in activity will contain a string (or an array of strings, as in the labels case) that is the "identifying string" of the associated resource (label.name for example). This denormalization of the data is intended to provide clients the ability to present meaningful information to the user about a modified association without the client having to look up/fetch (all) of the resources referenced.

As this information is captured in new_values, it represents the resource's identifying strings at the time the activity occurred, and not necessarily the current value at the time the activity structure is returned. If a client absolutely has to return the current name of a resource, it must dereference the resource ID also included in the new_values to obtain the resource's current state. On the other hand, if the resource has been deleted since the activity occurred, then this denormalization provides at least some user-meaningful information.

The original_values is the mirror-image of new_values and contains the values that the changed resource attributes had prior to the change being made. There will only be an original_values entry in the case where change_type is "update".

Local Updates from Changes

Some clients will be most efficiently implemented if they operate on the same basic set of information retrieved from Pivotal Tracker for a sustained period of time, and are capable of updating their locally-maintained copies of Tracker data in place, without re-fetching all of their data from Tracker periodically or when something indicates to them that data may have changed.

Such a client can fetch activity hashes and use the various new_values hashes in their changes as deltas that allow them to incrementally update their locally-stored data in place in order to stay in sync with what's stored by the Tracker server and seen by users viewing the Tracker web application. Because of Tracker's business rules, it is possible for seemingly simple client requests to have side effects that affect a number of otherwise-unrelated objects. Processing activity.changes allows a client to benefit from all of the server's logic to perform system-wide updates, as all changes carried out by the server will be represented by some new_values applied to one or more (possibly unanticipated) resources.

Basically, "all" the client has to do is process the entries from each activity's changes in the order that those activities have occurred. This means that in those cases where a GET from an activity endpoint returns multiple items, the last entry in the response array should be the first one processed, because the activity endpoints return data in reverse-chronologic order.

What merging the new_values into a client's representation of a particular resource entails is, of course, client specific. However, there are a number of easily-predicted side effects of some changes that will not be communicated by the server in changes because of the impact that would have on the size of the activity items. These are:

  • Changes in task.position. When a task is moved up within the list of tasks, that implicitly means that all of the other tasks that are below its new position are implicitly moved down. If your client is storing the position attribute of tasks explicitly, rather than (for example) keeping tasks in an array and determining position dynamically when needed based on a task's array index, then it is the client's responsibility to handle each change in task.position that occurs in changes by updating all necessary other position values of tasks on the same story.
  • Similarly, while changes to the before_id and after_id values for a story or epic (which are used as command parameters, although not currently returned as attributes of the resource from GET operations, which is why they are marked as Unimplemented in the story resource's reference description) are returned in activity for operations which have caused the resource to move relative to its siblings. However, the implicit changes to other stories/epics that were not otherwise modified by the operation are not included in changes. For example, in the case where a single story is moved from one position in the Backlog to another, only that story will occur in the changes array. However, if your client is storing its copies of story resource data in a doubly-linked list with explicit and stored before_id and after_id values, then four other stories will also have to be updated: the two stories that the moved story used to be between, and the two stories that are around the position that the moved story went to. The client is responsible for performing these updates if necessary.
  • Changes to iteration start, end, and stories list. A number of changes to story's and iteration_overrides can cause the arrangement of iterations in the project to change. If your client displays or otherwise needs to track which story is in which iteration, you will need to update or refetch the iteration information following one of these changes.
  • Changes to associations between resources are represented as changes to only one of the resources involved. For example, when a comment is added to a story (created), the activity's changes array will contain an entry for both the story and the comment. The entry for the comment will be a "create" change that lists the story_id of the story that it was added to. This is the entry that indicates the change in the association between the two resources. The entry for the story is present to communicate the new value for the story's updated_at attribute, but the comment_ids attribute never shows up in a changes list. If the client keeps an explicit copy of this array (rather than translating story comment arrays it receives into a more direct link between its internal resource objects), then it is responsible for making updates to it as comment resources that reference the story are created and deleted.
Data Integrity Over Time

Tracker currently makes the last six month's worth of data changes available through the activity endpoints. In almost all cases, the above statements about the content of an activity structure are correct. In some exceptional cases (more likely for activity from older operations) not all of the data will be available to completely fill in the activity structure. In particular:

  • In most cases, Tracker has "identifying strings" available even for resources that have been deleted by users. But in some cases, this isn't true. When this occurs, the API will return place-holder strings, for example "*deleted*", as the content of an identifying string. The exact content of a place-holder string is not necessarily stable over time, and it is recommended that clients allow them to pass through to user presentation, rather than trying to detect and handle them.
  • original_values: Again, in most cases Tracker has the complete set of pre-existing values for updated attributes. But not in all. Clients should be prepared to handle activity items that are missing keys from an original_values hash (cases where a key that occurs in new_values doesn't occur in original_values), and to handle updates in which the entire original_values hash is missing.
  • In some cases for labels that have been deleted, Tracker has been able to retain the denormalized name that the label had at the time the original operation was performed, but not the id value that the deleted label used to have. When returning activity for (the few) cases where this occurred, Tracker will fill in the missing id values with -1. Clients should be prepared to handle this in a way that is appropriate for them. (Of course, if the client uses only the strings and never tries to look up a resource referenced by activity structures, then it would never look at the various resource ids, and never actually notice this inconsistency.

Reports and Analytics

The Tracker API provides daily history and analytical information at the project, release and iteration level, and story progress for a given time frame. You can use this information to analyze project trends and produce reports of project and story progress.

All of the endpoints and resources highlighted below, either aggregate or expose data based on the basic building blocks of activity, story_transitions, and daily project_snapshots. Tracker currently makes the last six month's worth of reports and analytical data available. This may be increased in the future.

In an effort to make to make reports and analytics data available to everyone, all of the new endpoints support the CSV Response Format as well as the default JSON encoding.

Analytical Endpoints and Resources

These endpoints enable API users to analyze project trends, find bottlenecks, and opportunities for process improvement. The data can be accessed through a combination of iteration and story level analytical endpoints and resources.

Story transition data can be accessed directly or through aggregate cycle time data at the iteration or story level. Tracker defines cycle time as the total time spent in each state from the time a story was started until it's been accepted.

Iteration Analytics
The analytics resource provides cycle time, rejection rate, the number of new bugs created, and the number of stories accepted. It can be optionally returned as part of the iteration resource.
Story Cycle Time Details
The cycle_time_details resource provides detailed information around time spent and the number of times a story cycled through each state, excluding weekends. This resource can be optionally returned as part of the story resource or by the endpoint /projects/{project_id}/iterations/{iteration_number}/analytics/cycle_time_details for all of the stories in a given iteration.
Story Transitions
The /projects/{project_id}/story_transitions and /projects/{project_id}/stories/{story_id}/transitions endpoints give you access to all of the recorded stories' transitions. A story transition is a time based event describing when a story transitions to a new state. This is the raw data used to calculate the cycle time details for each story.

 

Historical Endpoints and Resources

The Project History endpoints provide a means to retrieve a daily history of story points, counts by state, per day information about story state, estimate and position in current, backlog, and the icebox. All history endpoints represent the state of the project at midnight in the project's timezone. To focus in on a specific feature or category of stories, you can filter history endpoints by label.

Project History
The endpoint /projects/{project_id}/history/days returns daily history (over the selected period) for the entire project. If no start date or end date is provided, the project's most recent history is returned.
Release History
The endpoint /projects/{project_id}/history/releases/{id}/days returns daily history of the release id specified in the path. To find releases in your project, use the Stories endpoint. Filter your search for unstarted releases by using the with_story_type=release and with_state=unstarted parameters.
Iteration History
The endpoint /projects/{project_id}/history/iterations/{iteration_number}/days returns the daily history of the iteration number specified in the path.
Project Snapshots
The endpoint /projects/{project_id}/history/snapshots returns a list of project_snapshots that provide per day information about story state, estimate and position in current, backlog, and the icebox.

Special Endpoint Usage

This section provides tutorial information for a number of different API operations whose semantics are more complicated than the straight-forward resource CRUD operations performed by the balance of the RESTy endpoints.

Manipulating Attachments on Comments

When comments (for stories or epics) are created or updated, they can have associated attachments, either files uploaded to Tracker or "files" stored within Google Apps. In both cases, the data must already exist before it can be associated with a comment being created. This section describes the sequence of operations used to create or access data for attachments, and how to create new comments that reference them.

Uploading File Attachments

Before being attached to a comment, a file must be uploaded to the Tracker server, which creates a new file_attachment resource. After it has been created, the information describing the file_attachment can be included in an operation that creates a new comment referencing it.

File attachments are uploaded to Tracker by POST'ing to the /projects/{project_id}/uploads endpoint. Unlike almost all other API endpoints, the request body POSTed to /projects/{project_id}/uploads is not expected to be JSON-encoded. Instead, it should be formatted and sent with the MIME type multipart/form-data. The following example shows a curl command that uploads a file from the local filesystem to Tracker.

export TOKEN='your Pivotal Tracker API token'

export FILE_PATH='/home/vader/art-projects/new-imperial-logo-6.jpg'

export PROJECT_ID=99

curl -X POST -H "X-TrackerToken: $TOKEN" -F file=@"$FILE_PATH" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/uploads"

View Response
Hide Response
Headers
Response Body
"{\"kind\":\"file_attachment\",\"id\":300,\"filename\":\"new-imperial-logo.jpg\",\"created_at\":\"2024-03-13T12:00:00Z\",\"uploader_id\":101,\"thumbnailable\":true,\"height\":1995,\"width\":2000,\"size\":96228,\"download_url\":\"/file_attachments/300/download\",\"content_type\":\"image/jpeg\",\"uploaded\":false,\"big_url\":\"#\",\"thumbnail_url\":\"#\"}"

The JSON structure in an upload request's response body identifies and describes the file_attachment within Tracker. file_attachment resources reference the attached file, but they aren't the file and they do not contain the file data. The entire JSON hash will have to be included by the client, unmodified, in future requests intended to include the attachment in a comment.

Note that the Content-type of the server response can lie. This behavior, while not strictly correct according to the protocol, is intended. There is a bug in Microsoft Internet Explorer that will cause it to navigate away from a page executing a JavaScript client app at the end of an upload if the server returns the (correct) type of application/json. The server will return the correct Content-type, instead of text/html, if the client includes application/json in the request's Accept header.

A Comment with File Attachments

Once one or more files have been uploaded to Tracker, and the client has collected the JSON hashes representing the file_attachment resource for each one from the server responses, a comment can be created that references the attachments.

There are a number of endpoints that create or update stories, epics, or comments that provide for the association of attachments with a comment—generally they will have a comment or comments parameter that accepts a nested hash representing the new comment(s). The exception are the endpoints that have no function other than to create a single new comment on either a story or an epic: /projects/{project_id}/stories/{story_id}/comments and /projects/{project_id}/epics/{epic_id}/comments. The parameters for these two endpoints are the individual components that make up a new comment, and also define the acceptable keys in nested resource hashes intended to create a new comment.

For example, the following request updates an existing story, changing its state to rejected and simultaneously adding a comment with a file_attachment:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X PUT -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"comment":{"text":"Here are the new plans.","file_attachments":[{"type":"file_attachment","id":21,"filename":"Corellian corvette deck plan.tiff","created_at":1710244800000,"uploader_id":101,"thumbnailable":false,"height":216,"width":650,"size":561878,"download_url":"/attachments/0000/0021/Corellian corvette deck plan_big.tiff","thumbnail_url":"/attachments/0000/0021/Corellian corvette deck plan_thumb.tiff"}]},"current_state":"rejected"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555?fields=requested_by%2Cowned_by%2Clabels%2Ccomments%28text%2Cfile_attachments%29%2Ctasks"

View Response
Hide Response
Headers
Response Body
{"comments":[{"text":"I want them alive!","file_attachments":[{"kind":"file_attachment","id":20,"filename":"empire.png","created_at":"2024-03-12T12:00:00Z","uploader_id":100,"thumbnailable":true,"height":804,"width":1000,"size":82382,"download_url":"/attachments/0000/0020/empire_big.png","thumbnail_url":"/attachments/0000/0020/empire_thumb.png"},{"kind":"file_attachment","id":21,"filename":"Corellian corvette deck plan.tiff","created_at":"2024-03-12T12:00:05Z","uploader_id":101,"thumbnailable":false,"height":216,"width":650,"size":561878,"download_url":"/attachments/0000/0021/Corellian corvette deck plan_big.tiff","thumbnail_url":"/attachments/0000/0021/Corellian corvette deck plan_thumb.tiff"}],"id":112},{"text":"Here are the new plans.","file_attachments":[{"kind":"file_attachment","id":21,"filename":"Corellian corvette deck plan.tiff","created_at":"2024-03-12T12:00:05Z","uploader_id":101,"thumbnailable":false,"height":216,"width":650,"size":561878,"download_url":"/attachments/0000/0021/Corellian corvette deck plan_big.tiff","thumbnail_url":"/attachments/0000/0021/Corellian corvette deck plan_thumb.tiff"}],"id":300}],"id":555,"labels":[],"requested_by":{"kind":"person","id":101,"name":"Darth Vader","email":"vader@deathstar.mil","initials":"DV","username":"vader"},"tasks":[]}

Note that the content of the file_attachment in the response (and there will only be a file_attachment in the response if fields is used to request it—the full nested hierarchy is not included in responses by default) may differ from what is sent by the client. The content of the file_attachment hash in the response will reflect the most recent state of the attachment the server has, while the hash included in the request parameters is used solely to identify the desired attachment.

Downloading File Attachments

To download a file that was attached to a comment, the client needs the unique ID of the attachment, which is available in the file_attachment_ids list in a comment resource. (Which the client can obtain from one of the .../comments endpoints or as a nested resource in another endpoint's result.)

The request path (endpoint) used to download attachment files is unversioned, and not under the /services/ URL space, but is still part of the Tracker API. GET requests may be made to the https://www.pivotaltracker.com/file_attachments/{file_attachment_id}/download endpoint to retrieve the attachment file content.

The download endpoint is consistent with other API endpoints for authentication and authorization, and in the case of failures will return a JSON error information hash. There are two primary differences between performing a GET request to this endpoint and the other API operations available:

  • The body of a successful response will be the originally-uploaded content of the attachment, with the associated MIME type. It will not be a JSON-encoded data structure as is returned by (almost all) of the other API requests. The attachment content will not be nested inside of a JSON response envelope structure.
  • The response to this request may be an HTTP redirect (status 303 with a Location: response header) indicating that the actual data can be obtained from a different server. The client should not store the redirect URLs. These are temporary and will become invalid a while after they are issued. (This is done to ensure the security of attachment data. The API token of a Tracker user who has been removed from a project will no longer be authorized to obtain attachment data from this endpoint, directly or through redirect. Ensuring that the redirect URLs become invalid over time prevents authorization from being circumvented by people storing them.)
Accessing a Google Attachment

When a client obtains a google_attachment resource from Tracker, using the information in it to access the Google Apps document itself can be accomplished using one or more of the Google Apps APIs. Note that separate authentication and authorization is required to access Google Apps directly—while a Tracker user profile can be linked to a Google Apps domain user, Tracker and Google Apps have separate API client authentication, and a Tracker API token will not provide access to Google Apps, even though Tracker ensures that the sharing on the Google Apps document will allow the client's user access to it.

A JavaScript client executing in a browser can delegate the access to Google Apps to the browser itself by navigating to (or opening a new tab/window) the URL provided in the alternate_link attribute of the google_attachment resource.

Tracker Updates in SCM Post-Commit Hooks

Using the /source_commits Endpoint

The Tracker API supports integration with post-commit hooks of Source Control Management (SCM) systems such as Git, Subversion, etc. When a commit is made to the SCM, a trigger can make a request to the Tracker API to add a story comment with the commit ID, author and message. It can also optionally change story state.

The request is POSTed to the /source_commits endpoint. For this request, the POST body should contain a single parameter, source_commit, which must be a hash whose structure matches the source_commit resource. In this structure, the message string is required, and optionally commit_id, url, and/or author may be supplied. The content of the message string is parsed by Tracker to determine what story or stories are affected by the commit, and what, if anything, the API request should do to those stories.

Like other API requests, POSTs to /source_commits must be authenticated and authorized. Authentication operates like other API endpoints, which implies that the actions taken by the /source_commits request are assumed to be on behalf of a specific, known Tracker user. While not mandatory, it is common practice to create a Tracker login (Profile) and API Token that will be used specifically for /source_commits requests (or for all your automated/integration API requests). Only those actions permissible to the user authenticated for the request can be performed by the API for /source_commits. Therefore, you will likely want to add your source-control user to each Tracker project that might be affected by a post-commit hook request.

To associate an SCM commit with specific Tracker stories, Tracker looks for a special syntax in the commit message string to indicate one or more story IDs and (optionally) a state change for the story. Your commit message should contain one pair of square brackets, containing one or more sets of a hash character followed immediately by a story ID.

export TOKEN='your Pivotal Tracker API token'

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"source_commit":{"commit_id":"abc123","message":"[Finishes #555] sweep of the ship confirms no life-forms remaining.","url":"http://example.com/abc123","author":"Darth Vader","repo":"reponame"}}' "https://www.pivotaltracker.com/services/v5/source_commits?fields=%3Adefault%2Ccomments"

View Response
Hide Response
Headers
Response Body
[{"kind":"story","comments":[{"kind":"comment","id":112,"story_id":555,"text":"I want them alive!","person_id":101,"created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"},{"kind":"comment","id":300,"story_id":555,"text":"Commit by Darth Vader\nhttp://example.com/abc123\nreponame\n\n[Finishes #555] sweep of the ship confirms no life-forms remaining.","person_id":101,"created_at":"2024-03-12T12:00:05Z","updated_at":"2024-03-12T12:00:05Z","commit_identifier":"abc123"}],"id":555,"project_id":99,"name":"Bring me the passengers","description":"ignore the droids","story_type":"feature","current_state":"finished","estimate":2,"requested_by_id":101,"owner_ids":[],"labels":[],"created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:05Z","url":"http://localhost/story/show/555","story_priority":"p3"}]

The minimum commit message string that will allow Tracker to associate a /source_commits POST with a story and create a comment is a single story ID enclosed in square brackets: '[#12345678]'. A more typical message, indicating that one commit completes two stories (which need not be in the same Tracker project), might look like this: 'finally [finished #12345678 #12345779], fixes client/server integration glitch'

If an included story was not already started (it was in the "not started" state), an update to that story from /source_commits that doesn't contain any other state-change information will automatically start the story.

To automatically finish a story by using a commit message, include "fixed", "completed", or "finished" in the square brackets in addition to the story ID. You may use different cases or forms of these verbs, such as "Fix" or "FIXES", and they may appear before or after the story ID. Note: For features, use of one of these keywords will put the story in the finished state. For chores, it will put the story in the accepted state.

In some environments, code that is committed is automatically deployed. For this situation, use the keyword "delivers" and feature stories will be put in the delivered state.

Subversion Example

The following is an example shell script that could be a Subversion post-commit hook script (don't forget to make it executable with chmod a+x [script-name]):

  #!/bin/sh
  set -e

  REPO="$1"
  REV="$2"
  TOKEN="$3"

  AUTHOR=`/usr/bin/svnlook author $REPO -r $REV`
  MESSAGE=`/usr/bin/svnlook log $REPO -r $REV`
  URL="http://example.com/the_url_to_show_this_revision_in_a_web_browser"

  # You must HTML-escape the JSON post data
  MESSAGE=${MESSAGE//&/&amp;}
  MESSAGE=${MESSAGE//</&lt;}
  MESSAGE=${MESSAGE//>/&gt;}
  # This is just a simple example.  You also need to handle the AUTHOR, as well as quotes, backticks, etc...
  # If you have a better one, please let us know, and we'll add it to the list of Tracker 3rd Party Tools
  # at http://pivotaltracker.com/integrations.

  RESPONSE=`curl -H "X-TrackerToken: $TOKEN" -X POST -H "Content-type: application/json" \
      -d "{\"source_commit\":{\"message\":\"$MESSAGE\", \"author\":\"$AUTHOR\", \"commit_id\":\"$REV\", \"url\":\"$URL\"}}" \
  https://www.pivotaltracker.com/services/v5/source_commits`

  echo $RESPONSE >> /tmp/tracker_post_commit.log

For more details, see the Subversion Book section on Implementing Repository Hooks.

GitHub

If you are using a Git repository hosted on GitHub, you can easily integrate it with Tracker. Just go to the Service Hooks page on GitHub for the repository that you want to send Tracker post-commit updates (https://github.com/[you]/[your-repo]/settings/hooks) and scroll down to Pivotal Tracker.

The story comments created by GitHub integration will also include the GitHub URL of the associated commit. For example:

  Commit: R2-D2
  http://github.com/empire/deathstar/commit/41a212ee83ca127e3c8cf465891ab7216a705f57
  [Fixes #12345677 #12345678] shut down all garbage smashers on the detention level
Non-GitHub Git

If you don't use GitHub to host your git repository, it is possible to write a git post-receive server script, similar to the Subversion Example above, although you'll want to make sure you handle each commit separately. There are various examples of git post commit hooks, including this one. If you do create one for Tracker, and would like to share it, please let us know, and we'll add it to the list of Tracker 3rd Party Tools.

Other SCM Tool Hooks

We welcome the Tracker community to contribute integration support for other Source Control Management systems and tools. If you have created something you would like to share, please let us know at support@pivotaltracker.com and we might add it to the list of Tracker Third Party Tools.

Manipulating Labels on Stories as Hashes

As a type of resource associated with a story, the set of labels on a particular story is represented by default as a list of label_ids. For responses returned from the Pivotal Tracker API, whether information on associated labels is included in a story at all, and how it is represented is determined by the defaults (see Default Response Content and story) and by the fields parameter that can be provided to most API requests (see Response-Controlling Parameters).

When an API request's parameters include the information that defines a story, it will generally allow the set of associated labels to be specified. The common behavior for the API is to accept either an array of label_ids or an array of labels—nested resource structures (hashes), each of which represents a label with which the story is associated. The general rules for using nested resources in parameters in an association are given above. This section presents concrete examples of creating and updating stories using nested resources instead of IDs to specify the story's labels.

A new story is created by POSTing to the /projects/{project_id}/stories endpoint. As it is created it can be associated with existing labels by providing their IDs in the label_ids parameter. Or, as shown below, each label can be identified by a nested structure in the labels parameter.

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"labels":[{"id":2008,"name":"rebel bases"},{"name":"newnew"}],"name":"Exhaust ports are ray shielded 👹"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories"

View Response
Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:05Z","current_state":"unscheduled","id":2300,"kind":"story","labels":[{"kind":"label","id":5100,"project_id":99,"name":"newnew","created_at":"2024-03-12T12:00:05Z","updated_at":"2024-03-12T12:00:05Z"},{"kind":"label","id":2008,"project_id":99,"name":"rebel bases","created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"}],"name":"Exhaust ports are ray shielded 👹","owner_ids":[],"project_id":99,"requested_by_id":101,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:05Z","url":"http://localhost/story/show/2300"}

In this example, the label "rebel bases" already existed in the project, so it has a server-assigned ID value, and the example assumes that the client already has obtained. For pre-existing labels (or other associated resources expressed through nested structures), the client must provide enough of the resource attributes to uniquely identify the resource. For a label, that means supplying id and/or name. If additional attributes are included, they have to match the same resource.

Alternatively, the label "newnew" did not already exist in the project. The label is created in the project implicitly by the story create operation, and associated with the new story. In the case of a new label, there is no pre-existing ID value, so the client must not include an id key in the request hash.

Updating the labels on a story using an array of resource hashes is comparable:

export TOKEN='your Pivotal Tracker API token'

export PROJECT_ID=99

curl -X PUT -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"labels":[{"name":"newnew"},{"name":"labellabel"},{"id":2011}]}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555"

Hide Response
Headers
Response Body
{"created_at":"2024-03-12T12:00:00Z","current_state":"unstarted","description":"ignore the droids","estimate":2,"id":555,"kind":"story","labels":[{"kind":"label","id":5101,"project_id":99,"name":"labellabel","created_at":"2024-03-12T12:00:05Z","updated_at":"2024-03-12T12:00:05Z"},{"kind":"label","id":2011,"project_id":99,"name":"mnt","created_at":"2024-03-12T12:00:00Z","updated_at":"2024-03-12T12:00:00Z"},{"kind":"label","id":5100,"project_id":99,"name":"newnew","created_at":"2024-03-12T12:00:05Z","updated_at":"2024-03-12T12:00:05Z"}],"name":"Bring me the passengers","owner_ids":[],"project_id":99,"requested_by_id":101,"story_priority":"p3","story_type":"feature","updated_at":"2024-03-12T12:00:05Z","url":"http://localhost/story/show/555"}

In this case, two new labels are created. (Assume that this example and the previous happened separately—not one after the other.)

Using the GET-Request Aggregator

In addition to the RESTful API endpoints described in the version-specific reference sections, Tracker supports a request aggregation endpoint that allows a client to bundle a group of GET requests into a single server operation. Note that this is intended primarily to improve server response time and minimize the number of network connections the client has to maintain. Logically, individual requests contained in the group sent to the aggregator are processed as if they were a set of independent requests received essentially simultaneously.

The aggregator is used by POSTing a JSON data structure containing an array of the URLs that the client wants to GET from. The aggregator will then perform independent GET requests to Tracker for each of those URLs, and compile the result into a JSON data structure containing a hash where each key is the URL fetched from and each value is the JSON response received from fetching that URL. This endpoint does not aggregate responses in the CSV format.

The example curl command below POSTs this JSON array of URLs

[ "/services/v5/projects/510733/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/646869/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/442903/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/367813/activity?limit=10&occurred_after=2014-09-01T00:00:00Z" ]

to the aggregator endpoint in order to fetch recent activity from the four referenced public projects. (Because these projects are public, there is no need to include an API token to authenticate the request.)

curl -X POST -H "Content-Type: application/json" -d '[ "/services/v5/projects/510733/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/646869/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/442903/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true", "/services/v5/projects/367813/activity?limit=10&occurred_after=2014-09-01T00:00:00Z" ]' "https://www.pivotaltracker.com/services/v5/aggregator"
Hide Response
Headers
Response Body
{"/services/v5/projects/646869/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true":{"http_status":"200","project_version":4486,"pagination":{"total":261,"limit":10,"offset":0,"returned":10},"data":[{"kind":"story_move_activity","guid":"646869_4486","project_version":4486,"message":"Skud moved this story before 'API demo: harvest calculator'","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":78620038,"original_values":{"updated_at":"2014-09-24T01:09:33Z","before_id":79411916,"after_id":46522011},"new_values":{"updated_at":"2014-09-24T01:43:00Z","before_id":79412010,"after_id":79411916},"name":"create framework for API v1","story_type":"feature"}],"primary_resources":[{"kind":"story","id":78620038,"name":"create framework for API v1","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/78620038"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:43:00Z"},{"kind":"story_update_activity","guid":"646869_4485","project_version":4485,"message":"Skud edited this feature","highlight":"edited","changes":[{"kind":"story","change_type":"update","id":79412508,"original_values":{"description":"Create a demo app (details TBD) which demonstrates the use of the geo features of the v1 API","updated_at":"2014-09-24T01:28:56Z"},"new_values":{"description":"Create a demo app (details TBD) which demonstrates the use of the geo features of the v1 API.","updated_at":"2014-09-24T01:28:59Z"},"name":"API demo: geolocation","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412508,"name":"API demo: geolocation","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412508"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:28:59Z"},{"kind":"comment_create_activity","guid":"646869_4484","project_version":4484,"message":"Skud added comment: \"Suggested demo app: based on a given location and distance, suggest planting dates for a given crop. (You can get this from looking at when others plant it.)\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":80388792,"new_values":{"id":80388792,"story_id":79412508,"text":"Suggested demo app: based on a given location and distance, suggest planting dates for a given crop. (You can get this from looking at when others plant it.)","person_id":784669,"created_at":"2014-09-24T01:28:56Z","updated_at":"2014-09-24T01:28:56Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":79412508,"original_values":{"follower_ids":[],"updated_at":"2014-09-24T01:28:14Z"},"new_values":{"follower_ids":[784669],"updated_at":"2014-09-24T01:28:56Z"},"name":"API demo: geolocation","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412508,"name":"API demo: geolocation","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412508"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:28:56Z"},{"kind":"story_move_activity","guid":"646869_4483","project_version":4483,"message":"Skud moved this story before 'Add date params to planting/harvest APIs'","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":79412508,"original_values":{"current_state":"unstarted","updated_at":"2014-09-24T01:28:03Z","before_id":78687644,"after_id":78615014},"new_values":{"current_state":"unscheduled","updated_at":"2014-09-24T01:28:14Z","before_id":79412364,"after_id":79412304},"name":"API demo: geolocation","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412508,"name":"API demo: geolocation","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412508"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:28:14Z"},{"kind":"story_update_activity","guid":"646869_4482","project_version":4482,"message":"Skud unstarted this feature","highlight":"unstarted","changes":[{"kind":"story","change_type":"update","id":79412508,"original_values":{"current_state":"started","updated_at":"2014-09-24T01:27:50Z"},"new_values":{"current_state":"unstarted","updated_at":"2014-09-24T01:28:03Z"},"name":"API demo: geolocation","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412508,"name":"API demo: geolocation","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412508"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:28:03Z"},{"kind":"story_update_activity","guid":"646869_4481","project_version":4481,"message":"Skud started this feature","highlight":"started","changes":[{"kind":"story","change_type":"update","id":79412508,"original_values":{"current_state":"unscheduled","owned_by_id":null,"owner_ids":[],"updated_at":"2014-09-24T01:23:41Z","before_id":79412364,"after_id":79412304},"new_values":{"current_state":"started","owned_by_id":784669,"owner_ids":[784669],"updated_at":"2014-09-24T01:27:50Z","before_id":78687644,"after_id":78615014},"name":"API demo: geolocation","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412508,"name":"API demo: geolocation","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412508"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:27:50Z"},{"kind":"story_move_activity","guid":"646869_4480","project_version":4480,"message":"Skud moved this story before 'Style email notifications with Bootstrap'","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":79412644,"original_values":{"updated_at":"2014-09-24T01:27:10Z","before_id":78620038,"after_id":46522011},"new_values":{"updated_at":"2014-09-24T01:27:18Z","before_id":78689818,"after_id":79412416},"name":"API demo: OAuth","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412644,"name":"API demo: OAuth","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412644"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:27:18Z"},{"kind":"story_create_activity","guid":"646869_4479","project_version":4479,"message":"Skud added this feature","highlight":"added","changes":[{"kind":"story","change_type":"create","id":79412644,"new_values":{"id":79412644,"project_id":646869,"name":"API demo: OAuth","description":"Write a script/example to demonstrate how to authenticate against Growstuff using OAuth","story_type":"feature","current_state":"unscheduled","estimate":2,"requested_by_id":784669,"owner_ids":[],"label_ids":[4139945,9521066,9521062],"follower_ids":[],"created_at":"2014-09-24T01:27:10Z","updated_at":"2014-09-24T01:27:10Z","before_id":78620038,"after_id":46522011,"labels":["api","api-example","api-project"]},"name":"API demo: OAuth","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412644,"name":"API demo: OAuth","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412644"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:27:10Z"},{"kind":"story_move_activity","guid":"646869_4478","project_version":4478,"message":"Skud moved this story before 'Set up OAuth authentication for API etc'","highlight":"moved","changes":[{"kind":"story","change_type":"update","id":79412602,"original_values":{"updated_at":"2014-09-24T01:26:11Z","before_id":78620038,"after_id":46522011},"new_values":{"updated_at":"2014-09-24T01:26:17Z","before_id":79412416,"after_id":79412364},"name":"API demo: date parameters","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412602,"name":"API demo: date parameters","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412602"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:26:17Z"},{"kind":"story_create_activity","guid":"646869_4477","project_version":4477,"message":"Skud added this feature","highlight":"added","changes":[{"kind":"story","change_type":"create","id":79412602,"new_values":{"id":79412602,"project_id":646869,"name":"API demo: date parameters","description":"Create a demo (details TBD) to demonstrate the date-related query parameters of the v1 API","story_type":"feature","current_state":"unscheduled","estimate":2,"requested_by_id":784669,"owner_ids":[],"label_ids":[4139945,9521066,9521062],"follower_ids":[],"created_at":"2014-09-24T01:26:11Z","updated_at":"2014-09-24T01:26:11Z","before_id":78620038,"after_id":46522011,"labels":["api","api-example","api-project"]},"name":"API demo: date parameters","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79412602,"name":"API demo: date parameters","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79412602"}],"project":{"kind":"project","id":646869,"name":"growstuff"},"performed_by":{"kind":"person","id":784669,"name":"Skud","initials":"Skud"},"occurred_at":"2014-09-24T01:26:11Z"}]},"/services/v5/projects/367813/activity?limit=10&occurred_after=2014-09-01T00:00:00Z":[],"/services/v5/projects/442903/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true":{"http_status":"200","project_version":28396,"pagination":{"total":35,"limit":10,"offset":0,"returned":10},"data":[{"kind":"story_move_into_project_activity","guid":"442903_28396","project_version":28396,"message":"Jen Goree moved \"Snapshot of atom trace doesn't seem to be captured.\" into this project from Lightweight Activity Runtime and Authoring","highlight":"moved","changes":[{"kind":"story","change_type":"create","id":77688004,"new_values":{"id":77688004,"project_id":442903,"name":"Snapshot of atom trace doesn't seem to be captured.","description":"Run the model on this page and then try to take a snapshot.\nhttp://authoring.concord.org/activities/539/pages/3421","story_type":"bug","current_state":"unstarted","requested_by_id":46555,"owner_ids":[],"label_ids":[],"follower_ids":[],"created_at":"2014-08-27T04:28:32Z","updated_at":"2014-09-05T15:49:42Z","before_id":78127908,"after_id":69671760,"labels":[]},"name":"Snapshot of atom trace doesn't seem to be captured.","story_type":"bug"}],"primary_resources":[{"kind":"story","id":77688004,"name":"Snapshot of atom trace doesn't seem to be captured.","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/77688004"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":643251,"name":"Jen Goree","initials":"JG"},"occurred_at":"2014-09-05T15:49:42Z"},{"kind":"comment_create_activity","guid":"442903_28395","project_version":28395,"message":"Richard Klancer added comment: \"Thanks, Piotr, that explanation of the background helps me to understand why it works that way. You're right, unfortunately I'm not going to fix the bug right now because I have a little bit of uncertainty regarding some of the intent of the code, and because of the requirement to test so many interactives...\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":78730130,"new_values":{"id":78730130,"story_id":78127908,"text":"Thanks, Piotr, that explanation of the background helps me to understand why it works that way. You're right, unfortunately I'm not going to fix the bug right now because I have a little bit of uncertainty regarding some of the intent of the code, and because of the requirement to test so many interactives...","person_id":56694,"created_at":"2014-09-04T15:54:48Z","updated_at":"2014-09-04T15:54:48Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":78127908,"original_values":{"updated_at":"2014-09-03T17:49:13Z"},"new_values":{"updated_at":"2014-09-04T15:54:48Z"},"name":"Setting a component's width to \"interactive.width\" breaks the layout system","story_type":"bug"}],"primary_resources":[{"kind":"story","id":78127908,"name":"Setting a component's width to \"interactive.width\" breaks the layout system","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/78127908"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T15:54:48Z"},{"kind":"comment_delete_activity","guid":"442903_28394","project_version":28394,"message":"Richard Klancer removed comment: \"Unstarting this particular story. Refer to #78159500 for fixes to DNA interactives; https://www.pivotaltracker.com/projects/442903/stories/78127908 for problem with semantic layout system.\"","highlight":"removed comment:","changes":[{"kind":"story","change_type":"update","id":77758360,"original_values":{"updated_at":"2014-09-04T14:30:33Z"},"new_values":{"updated_at":"2014-09-04T14:30:37Z"},"name":"A model can be center aligned in an interactive.","story_type":"feature"},{"kind":"comment","change_type":"delete","id":78653862}],"primary_resources":[{"kind":"story","id":77758360,"name":"A model can be center aligned in an interactive.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/77758360"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:30:37Z"},{"kind":"comment_create_activity","guid":"442903_28393","project_version":28393,"message":"Richard Klancer added comment: \"Unstarting this particular story. Refer to #69926466 for fixes to DNA interactives; #78127908 for problem with semantic layout system.\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":78717732,"new_values":{"id":78717732,"story_id":77758360,"text":"Unstarting this particular story. Refer to #69926466 for fixes to DNA interactives; #78127908 for problem with semantic layout system.","person_id":56694,"created_at":"2014-09-04T14:30:33Z","updated_at":"2014-09-04T14:30:33Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":77758360,"original_values":{"updated_at":"2014-09-03T23:04:24Z"},"new_values":{"updated_at":"2014-09-04T14:30:33Z"},"name":"A model can be center aligned in an interactive.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":77758360,"name":"A model can be center aligned in an interactive.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/77758360"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:30:33Z"},{"kind":"story_delete_activity","guid":"442903_28392","project_version":28392,"message":"Richard Klancer deleted this bug","highlight":"deleted","changes":[{"kind":"comment","change_type":"delete","id":78653626},{"kind":"story","change_type":"delete","id":78159500,"name":"Poor layout of DNA interactives","story_type":"bug"}],"primary_resources":[{"kind":"story","id":78159500,"name":"Poor layout of DNA interactives","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/78159500"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:29:53Z"},{"kind":"story_update_activity","guid":"442903_28391","project_version":28391,"message":"Richard Klancer finished this bug","highlight":"finished","changes":[{"kind":"story","change_type":"update","id":69926466,"original_values":{"current_state":"started","updated_at":"2014-09-04T14:29:32Z"},"new_values":{"current_state":"finished","updated_at":"2014-09-04T14:29:37Z"},"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug"}],"primary_resources":[{"kind":"story","id":69926466,"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/69926466"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:29:37Z"},{"kind":"story_update_activity","guid":"442903_28390","project_version":28390,"message":"Richard Klancer started this bug","highlight":"started","changes":[{"kind":"story","change_type":"update","id":69926466,"original_values":{"current_state":"unscheduled","owned_by_id":null,"owner_ids":[],"updated_at":"2014-09-04T14:29:29Z","before_id":69785480,"after_id":69943376},"new_values":{"current_state":"started","owned_by_id":56694,"owner_ids":[56694],"updated_at":"2014-09-04T14:29:32Z","before_id":77758360,"after_id":78159500},"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug"}],"primary_resources":[{"kind":"story","id":69926466,"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/69926466"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:29:32Z"},{"kind":"story_update_activity","guid":"442903_28389","project_version":28389,"message":"Richard Klancer edited this bug","highlight":"edited","changes":[{"kind":"story","change_type":"update","id":69926466,"original_values":{"story_type":"feature","updated_at":"2014-09-04T14:29:13Z"},"new_values":{"story_type":"bug","updated_at":"2014-09-04T14:29:29Z"},"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug"}],"primary_resources":[{"kind":"story","id":69926466,"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/69926466"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:29:29Z"},{"kind":"comment_create_activity","guid":"442903_28388","project_version":28388,"message":"Richard Klancer added comment: \"Commit by Richard Klancer\nImprove small-size layout of DNA interactives [#78159500]\nhttps://github.com/concord-consortium/lab-interactives-site/commit/ac15207762c0ca9b32d13804694b31fe08a498e6\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":78717526,"new_values":{"id":78717526,"story_id":69926466,"text":"Commit by Richard Klancer\nImprove small-size layout of DNA interactives [#78159500]\nhttps://github.com/concord-consortium/lab-interactives-site/commit/ac15207762c0ca9b32d13804694b31fe08a498e6","person_id":56694,"created_at":"2014-09-04T14:29:13Z","updated_at":"2014-09-04T14:29:13Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":69926466,"original_values":{"follower_ids":[39507,46555],"updated_at":"2014-08-29T19:39:28Z"},"new_values":{"follower_ids":[39507,46555,56694],"updated_at":"2014-09-04T14:29:13Z"},"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug"}],"primary_resources":[{"kind":"story","id":69926466,"name":"Layout of several DNA interactives should be improved so it is more resilient","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/69926466"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-04T14:29:13Z"},{"kind":"comment_create_activity","guid":"442903_28387","project_version":28387,"message":"Richard Klancer added comment: \"Unstarting this particular story. Refer to #78159500 for fixes to DNA interactives; https://www.pivotaltracker.com/projects/442903/stories/78127908 for problem with semantic layout system.\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":78653862,"new_values":{"id":78653862,"story_id":77758360,"text":"Unstarting this particular story. Refer to #78159500 for fixes to DNA interactives; https://www.pivotaltracker.com/projects/442903/stories/78127908 for problem with semantic layout system.","person_id":56694,"created_at":"2014-09-03T23:04:24Z","updated_at":"2014-09-03T23:04:24Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":77758360,"original_values":{"updated_at":"2014-09-03T23:03:35Z"},"new_values":{"updated_at":"2014-09-03T23:04:24Z"},"name":"A model can be center aligned in an interactive.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":77758360,"name":"A model can be center aligned in an interactive.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/77758360"}],"project":{"kind":"project","id":442903,"name":"Lab"},"performed_by":{"kind":"person","id":56694,"name":"Richard Klancer","initials":"RK"},"occurred_at":"2014-09-03T23:04:24Z"}]},"/services/v5/projects/510733/activity?limit=10&occurred_after=2014-09-01T00:00:00Z&envelope=true":{"http_status":"200","project_version":13471,"pagination":{"total":786,"limit":10,"offset":0,"returned":10},"data":[{"kind":"story_update_activity","guid":"510733_13471","project_version":13471,"message":"Jasson McMorris accepted this feature","highlight":"accepted","changes":[{"kind":"story","change_type":"update","id":79407330,"original_values":{"current_state":"delivered","accepted_at":null,"updated_at":"2014-09-24T03:19:05Z","before_id":79230628,"after_id":70499300},"new_values":{"current_state":"accepted","accepted_at":"2014-09-24T03:19:24Z","updated_at":"2014-09-24T03:19:06Z","before_id":79332004,"after_id":79407420},"name":"Make The \"Use\" talents not level.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79407330,"name":"Make The \"Use\" talents not level.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79407330"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:19:06Z"},{"kind":"story_update_activity","guid":"510733_13470","project_version":13470,"message":"Jasson McMorris delivered this feature","highlight":"delivered","changes":[{"kind":"story","change_type":"update","id":79407330,"original_values":{"current_state":"finished","updated_at":"2014-09-24T03:19:04Z"},"new_values":{"current_state":"delivered","updated_at":"2014-09-24T03:19:05Z"},"name":"Make The \"Use\" talents not level.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79407330,"name":"Make The \"Use\" talents not level.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79407330"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:19:05Z"},{"kind":"story_update_activity","guid":"510733_13469","project_version":13469,"message":"Jasson McMorris finished this feature","highlight":"finished","changes":[{"kind":"story","change_type":"update","id":79407330,"original_values":{"current_state":"started","updated_at":"2014-09-24T03:14:06Z"},"new_values":{"current_state":"finished","updated_at":"2014-09-24T03:19:04Z"},"name":"Make The \"Use\" talents not level.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79407330,"name":"Make The \"Use\" talents not level.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79407330"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:19:04Z"},{"kind":"story_update_activity","guid":"510733_13468","project_version":13468,"message":"Jasson McMorris started this feature","highlight":"started","changes":[{"kind":"story","change_type":"update","id":79407330,"original_values":{"current_state":"unscheduled","owned_by_id":null,"owner_ids":[],"updated_at":"2014-09-24T03:14:03Z","before_id":79332418,"after_id":31918067},"new_values":{"current_state":"started","owned_by_id":614813,"owner_ids":[614813],"updated_at":"2014-09-24T03:14:06Z","before_id":79230628,"after_id":70499300},"name":"Make The \"Use\" talents not level.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79407330,"name":"Make The \"Use\" talents not level.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79407330"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:14:06Z"},{"kind":"story_update_activity","guid":"510733_13467","project_version":13467,"message":"Jasson McMorris estimated this feature as 0 points","highlight":"estimated","changes":[{"kind":"story","change_type":"update","id":79407330,"original_values":{"estimate":null,"updated_at":"2014-09-23T23:12:08Z"},"new_values":{"estimate":0,"updated_at":"2014-09-24T03:14:03Z"},"name":"Make The \"Use\" talents not level.","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79407330,"name":"Make The \"Use\" talents not level.","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79407330"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:14:03Z"},{"kind":"story_delete_activity","guid":"510733_13466","project_version":13466,"message":"Jasson McMorris deleted this feature","highlight":"deleted","changes":[{"kind":"comment","change_type":"delete","id":80392132},{"kind":"story","change_type":"delete","id":79408966,"name":"Explorer Gloves need art work","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79408966,"name":"Explorer Gloves need art work","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79408966"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:13:57Z"},{"kind":"comment_create_activity","guid":"510733_13465","project_version":13465,"message":"Jasson McMorris added comment: \"Already have one for this: https://www.pivotaltracker.com/story/show/73665740\"","highlight":"added comment:","changes":[{"kind":"comment","change_type":"create","id":80392132,"new_values":{"id":80392132,"story_id":79408966,"text":"Already have one for this: https://www.pivotaltracker.com/story/show/73665740","person_id":614813,"created_at":"2014-09-24T03:13:50Z","updated_at":"2014-09-24T03:13:50Z","file_attachment_ids":[],"google_attachment_ids":[],"file_attachments":[],"google_attachments":[]}},{"kind":"story","change_type":"update","id":79408966,"original_values":{"follower_ids":[],"updated_at":"2014-09-23T23:48:20Z"},"new_values":{"follower_ids":[614813],"updated_at":"2014-09-24T03:13:50Z"},"name":"Explorer Gloves need art work","story_type":"feature"}],"primary_resources":[{"kind":"story","id":79408966,"name":"Explorer Gloves need art work","story_type":"feature","url":"https://www.pivotaltracker.com/story/show/79408966"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:13:50Z"},{"kind":"story_update_activity","guid":"510733_13464","project_version":13464,"message":"Jasson McMorris accepted this bug","highlight":"accepted","changes":[{"kind":"story","change_type":"update","id":79407420,"original_values":{"current_state":"delivered","accepted_at":null,"updated_at":"2014-09-24T03:13:12Z","before_id":79230628,"after_id":70499300},"new_values":{"current_state":"accepted","accepted_at":"2014-09-24T03:13:31Z","updated_at":"2014-09-24T03:13:13Z","before_id":79332004,"after_id":79415050},"name":"Double Mattock","story_type":"bug"}],"primary_resources":[{"kind":"story","id":79407420,"name":"Double Mattock","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/79407420"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:13:13Z"},{"kind":"story_update_activity","guid":"510733_13463","project_version":13463,"message":"Jasson McMorris delivered this bug","highlight":"delivered","changes":[{"kind":"story","change_type":"update","id":79407420,"original_values":{"current_state":"finished"},"new_values":{"current_state":"delivered"},"name":"Double Mattock","story_type":"bug"}],"primary_resources":[{"kind":"story","id":79407420,"name":"Double Mattock","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/79407420"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:13:12Z"},{"kind":"story_update_activity","guid":"510733_13462","project_version":13462,"message":"Jasson McMorris finished this bug","highlight":"finished","changes":[{"kind":"story","change_type":"update","id":79407420,"original_values":{"current_state":"started","updated_at":"2014-09-24T03:13:10Z"},"new_values":{"current_state":"finished","updated_at":"2014-09-24T03:13:12Z"},"name":"Double Mattock","story_type":"bug"}],"primary_resources":[{"kind":"story","id":79407420,"name":"Double Mattock","story_type":"bug","url":"https://www.pivotaltracker.com/story/show/79407420"}],"project":{"kind":"project","id":510733,"name":"Crea"},"performed_by":{"kind":"person","id":614813,"name":"Jasson McMorris","initials":"JM"},"occurred_at":"2014-09-24T03:13:12Z"}]}}

The request headers from the POST to the aggregator endpoint are copied to each internal GET operation generated. However response headers produced by the individual requested GET operations are not retained or merged, so if your system uses any of the information normally returned via header, you'll want to use the Envelope Return Structure to get that information included in the encapsulated JSON responses. Note that in the example above, different formatting parameters are included in different URLs.

If an error occurs on one of the GET operations the aggregator is requested to perform, that is reflected in the result structure under that URL in the aggregator's response. Only errors that prevent the aggregator from operating on a request at all will be reflected in the HTTP response code it produces.

Developer Branding Guidelines

We’re thrilled that you’re interested in using the Pivotal Tracker API. We want to stay out of your way, so we’ve kept our branding guidelines as simple as possible.

Please send an email to support@pivotaltracker.com if you have any questions.