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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12:00:00Z","updated_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
[{"kind":"story","id":559,"created_at":1724155200000,"updated_at":1724155200000,"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":1724155200000,"updated_at":1724155200000}],"owned_by_id":104},{"kind":"story","id":561,"created_at":1724155200000,"updated_at":1724155200000,"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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
"{\"kind\":\"file_attachment\",\"id\":300,\"filename\":\"new-imperial-logo.jpg\",\"created_at\":\"2024-08-21T12: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-08-20T12: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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12: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"
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"
Headers
Response Body
{"account_id":100,"atom_enabled":true,"automatic_planning":true,"bugs_and_chores_are_estimatable":false,"created_at":"2024-08-20T12: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":"2024-05-06","start_time":"2024-08-20T12:00:10Z","time_zone":{"kind":"time_zone","olson_name":"America/Los_Angeles","offset":"-07:00"},"updated_at":"2024-08-20T12: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"
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 eachstory
and alabel_id
on eachepic
.- 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 anumber
, 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 underlyingiteration
, associated by having a matchingnumber
. Whileiteration
s 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
account
s 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
andfalse
. 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, like1367296015000
. 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 havedatetime
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 parameterdate_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 likedatetime
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 asquery
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 underdata
contains attributes from astory
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 todeprecation_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"
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"
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 thefields
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 theerror
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 afield
key with the name of a request parameter (resource attribute), and aproblem
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-08-20T12:00:00Z","estimate":17,"name":"Build out entire planet-destroying laser"}' "https:/ /www.pivotaltracker.com/ services/ v5/ projects/ $PROJECT_ID/ stories"
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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12:00:00Z","updated_at":"2024-08-20T12:00:00Z"},"name":"Rebel Home Worlds","project_id":99,"updated_at":"2024-08-20T12:00:00Z","url":"http://localhost/epic/show/5"}
curl -X GET "https:/ /www.pivotaltracker.com/ services/ v5/ epics/ 5?token=VadersToken"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12:00:00Z","updated_at":"2024-08-20T12:00:00Z"},"name":"Rebel Home Worlds","project_id":99,"updated_at":"2024-08-20T12: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"
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"
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 theemail
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 receivingemail
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 apoint_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 thepoint_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 afloat
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 theactivity
represents. There are no guarantees as to the format or content of this string over time, except that the content of thehighlight
from the sameactivity
record will be a substring ofmessage
. If you want to extract information from anactivity
, you should parse the object structure and not the content of themessage
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 eachactivity
'skind
is a string made up of a resource name and a type of operation performed on that resource, it is possible for clients to encounteractivity
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 themessage
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 thechanges
array of allactivity
structures received, as it is very likely it will find changes to resources it does recognize even in a type ofactivity
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 defaultrequested_by_id
, and now contains a nested resource structure containing the information for the person. -
owned_by
is not present, just asowned_by_id
wasn't present in the original example because (presumably) the story has no owner. Becauseowned_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 defaultlabels
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 thoughkind
was not included in thefields
list and there is nokind
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 (nooffset
provided) and the first item that is desired in the response to the current request. If nooffset
parameter is supplied for a page-able request, the default offset used will be 0. Most page-able requests return the first available item withoffset=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 nolimit
parameter is supplied for a paginated request, a default limit value will be used. If thelimit
parameter value supplied is larger than the maximumlimit
currently available through the endpoint, thelimit
used is capped at the maximum, and the value used, rather than the value from the request, will be reflected in thepagination.limit
(orX-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"
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-08-20T12: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-08-20T12: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-08-20T12:00:00Z","updated_at":"2024-08-20T12: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-08-20T12: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 thekind
attribute of the resource that is being referenced by thisprimary_resources
entry. -
id
ornumber
— This is the unique identifier for the particular resource being referenced. As always, iteration and iteration_override havenumber
while other persistent resources haveid
. -
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 isname
for stories, epics, projects and other resources with aname
attribute. The key isdescription
for tasks andtext
for comments. The resources for which no identifying string is available include things likeiteration
anditeration_override
. -
Sometimes
story_type
— when the resource is a story, its hash inprimary_resources
will contain this additional key. -
Sometimes
url
— when the resource is a story or an epic, its hash inprimary_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 kind
s
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 theposition
attribute oftask
s explicitly, rather than (for example) keeping tasks in an array and determiningposition
dynamically when needed based on a task's array index, then it is the client's responsibility to handle each change intask.position
that occurs inchanges
by updating all necessary otherposition
values of tasks on the same story. -
Similarly, while changes to the
before_id
andafter_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 inactivity
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 inchanges
. 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 thechanges
array. However, if your client is storing its copies of story resource data in a doubly-linked list with explicit and storedbefore_id
andafter_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'schanges
array will contain an entry for both the story and the comment. The entry for the comment will be a"create"
change that lists thestory_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'supdated_at
attribute, but thecomment_ids
attribute never shows up in achanges
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 handleactivity
items that are missing keys from anoriginal_values
hash (cases where a key that occurs innew_values
doesn't occur inoriginal_values
), and to handle updates in which the entireoriginal_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 returningactivity
for (the few) cases where this occurred, Tracker will fill in the missingid
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 byactivity
structures, then it would never look at the various resourceid
s, 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"
Headers
Response Body
"{\"kind\":\"file_attachment\",\"id\":300,\"filename\":\"new-imperial-logo.jpg\",\"created_at\":\"2024-08-21T12: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":1724155200000,"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"
Headers
Response Body
{"comments":[{"text":"I want them alive!","file_attachments":[{"kind":"file_attachment","id":20,"filename":"empire.png","created_at":"2024-08-20T12: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-08-20T12: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-08-20T12: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"
Headers
Response Body
[{"kind":"story","comments":[{"kind":"comment","id":112,"story_id":555,"text":"I want them alive!","person_id":101,"created_at":"2024-08-20T12:00:00Z","updated_at":"2024-08-20T12: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-08-20T12:00:05Z","updated_at":"2024-08-20T12: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-08-20T12:00:00Z","updated_at":"2024-08-20T12: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//&/&} MESSAGE=${MESSAGE//</<} MESSAGE=${MESSAGE//>/>} # 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"
Headers
Response Body
{"created_at":"2024-08-20T12:00:05Z","current_state":"unscheduled","id":2300,"kind":"story","labels":[{"kind":"label","id":5100,"project_id":99,"name":"newnew","created_at":"2024-08-20T12:00:05Z","updated_at":"2024-08-20T12:00:05Z"},{"kind":"label","id":2008,"project_id":99,"name":"rebel bases","created_at":"2024-08-20T12:00:00Z","updated_at":"2024-08-20T12: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-08-20T12: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"
Headers
Response Body
{"created_at":"2024-08-20T12: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-08-20T12:00:05Z","updated_at":"2024-08-20T12:00:05Z"},{"kind":"label","id":2011,"project_id":99,"name":"mnt","created_at":"2024-08-20T12:00:00Z","updated_at":"2024-08-20T12:00:00Z"},{"kind":"label","id":5100,"project_id":99,"name":"newnew","created_at":"2024-08-20T12:00:05Z","updated_at":"2024-08-20T12: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-08-20T12: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"
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.