Migrating to API v5

This article is intended to help developers port existing software that uses a deprecated version of the Pivotal Tracker API to the current version (version 5, referred to as “v5”). Please see the reference documentation for complete information on the current API. The v5 API will be the only available version, and there are currently no plans for a newer version.

The older v3 and v4 versions of the Tracker API will be removed on December 31, 2017. Applications using v3 or v4 will no longer work after this date. Any v3/v4 requests sent will receive an error response.

If you are already using the current v5 API, you won’t be affected.

The overview of primary similarities and differences is followed by a set of detailed steps to migrate your v3 client applications and take the best advantage of the features in v5. This is followed by a table of equivalent URLs and resources to help you upgrade your application code. The last section includes detailed examples of v3 requests and their v5 replacements.

Overview: Primary similarities and differences between API v3 and v5

You’ll have to make some significant changes to update your application to use API v5.

Similarities

  • Authentication for all Tracker v5 API requests is done via API Token, either in the HTTP header or as a query parameter.
  • Basic-auth can be used to retrieve the appropriate user authentication token. However, the API endpoint used for this changed between v3 and v5.

Differences

  • v5 request and response bodies are encoded in JSON, except for some endpoints that can encode responses in CSV format.
  • v5 supports two datetime formats.
  • v5 supports standardized requestor customization of the response content via the fields request parameter.
  • v5 API responses may be wrapped in a standard “envelope” structure that includes response metadata at the top level, or response metadata can be placed in HTTP response headers.
  • v5 error responses are more informative, with a standardized structure. Client software may display error message strings from responses directly to the user.
  • v5 has a standardized pagination mechanism for endpoints that support pagination.
  • v5 performs rate limiting at a more granular level, and most arbitrary response size limits present in v3 are replaced by pagination.
  • v3 adjusted datetime values based on the requesting user’s timezone preferences; v5 does not.

Steps for migrating v3 client applications to use API v5

Follow these steps to upgrade your application to work with API v5.

Change request URL version

URLs for Pivotal Tracker API requests continue to start with /services followed by a path component that specifies the version number. You should be able to update your code’s request URL generation to start API request base URLs with /services/v5/ in place of /services/v3.

Change request URL paths

Both API v3 and v5 follow many of the conventions of a “RESTful” API (i.e., one that follows the Representational State Transfer pattern). In particular, both identify logical resources stored on the server using URLs with path components matching the name of the type of resource and the ID number of a particular resource. Given this, while the body encoding has changed from XML to JSON from v3 to v5, and many request parameters have changed, most URL paths after the API version number are the same.

There are two primary exceptions to this:

  1. The resource that represents a comment made on a story or epic has changed from v3’s note to comment in v5. This change reflects the nomenclature that has been present in Tracker’s web UI for years, but which was changed after v3 was frozen.
  2. The resource that represents attachments included in comments was treated as a child of a story in v3 (/services/v3/stories/##/attachments), but is a child of a comment in v5 (/services/v3/stories/##/comments/##/attachments). This change both better represents the relationship between comments and attachments that Tracker presents to the user, and allows for things like retrieving all attachments on a particular comment or adding an attachment to an existing comment.

Change response parsing

Possibly the most significant change from v3 to v5 is replacing XML with JSON to encode the state of resources for virtually all endpoints. This will require the replacement of the code that extracted resource attribute values from v3 response bodies.

To ensure forward compatibility with updates made to Tracker that do not require an API version change, your code should:

  1. Use a fully featured and standard-compliant JSON parser to process API responses. For an introduction, see json.org, and for reference, see ECMA-404/RFC7159.
  2. Be capable of ignoring hash (object) keys that you don’t use.

A number of v3 client applications were written to parse API responses with templates or regular expressions, which made them sensitive to the introduction of new elements or attributes and to changes in the order in which data was included in the response. We kept the format of v3 responses consistent enough over time that the majority of such clients had long lifespans despite their brittleness. The same frozen formatting is not present in v5.

For example, Pivotal added the item blockers to the response provided from stories endpoints after the v5 API was published, without increasing the API version number beyond 5. See the API Versioning section of the API documentation.

Change request body formatting

Similarly, API requests that include bodies now (almost all) use JSON. You can generate a JSON-encoded body in any manner that is convenient and produces standard-compliant results. For forward compatibility, you should also ensure that the Content-type header included in API requests with JSON bodies is application/json. In the future, we may add support for alternative body formats without incrementing the API version number, and proper use of Content-type headers will ensure that your JSON-format data will always be treated as such by the API.

Note also that JSON preserves the type of resource attribute values, where XML encoded all values as strings and depended on element attributes or convention to values that were interpreted correctly. This means that it is important when formatting your request bodies that you don’t enclose numeric values in quotation marks; distinguish arrays and objects with the correct syntax ([] versus {}), etc. in order to avoid error responses when the API servers parse your requests.

Change datetime format handling

API v5 supports two alternative encodings for datetime values: either an integer value of milliseconds since the epoch (matching JavaScript conventions) or a human-readable string composed in compliance with ISO-8601, the “Data elements and interchange formats” standard. See the “datetime” item in API Data Types in the API documentation.

You will likely want your application to always receive datetime values in the same format. The format is controlled by the date_format parameter that can be added as a query parameter to the end of (almost) all v5 API requests.

Note that the convention used by the v3 API was to adjust datetime values to match the time zone of the Tracker user on whose behalf the API request is being made; v5 does not do this. Integer values are a raw count of milliseconds, and are therefore time zone independent. ISO-8601 values include an indicator of the time zone, but the zone chosen by Tracker when creating these strings is unrelated to user time zone selection. v5 API client software that chooses ISO-8601 datetime formats must be capable of handling any time zone offset permitted by the standard. If your software needs to display datetime values to the user in their preferred time zone, you can obtain their selection from the time_zone attribute of the me resource, which can be retrieved from the endpoint /services/v5/me.

Review request header creation/content

In many cases, the v3 API was incapable of adjusting its behavior in response to client preferences, and therefore ignored HTTP request headers such as Accept, Accept-Charset, and Accept-Encoding that allow for dynamic customization of the exchange between the client and server. v5 adheres to the standard uses of these headers and will use them for forward compatibility as API enhancements are introduced.

Your software should send request headers that match its actual capabilities. For example, a migrated client will currently be written to consume JSON response bodies. We may add support for additional, non–JSON response body formats that the client can request by sending an Accept value other than application/json. To ensure your client is forward compatible with such a change, you should send Accept: application/json now.

Improve error handling

Error handling in the v3 API was sparse. The API seldom returned response bodies in the case of errors that were more informative than the HTTP status codes themselves, and when it did, the formatting wasn’t standardized. This made error responses useful primarily to developers and unlikely to be handled by the client software.

In the event of errors during processing of client requests, v5 API endpoints respond with a standard data structure (see Error Responses in the v5 documentation). There are a number of named message-like strings that may occur in the error structure. Of these, requirement and general problem are most likely to be suitable for use directly as messages to the user of your client software. When present, possible_fix is typically directed toward the API developer. Similarly, the messages within the validation_errors object, when present, are intended to be suitable for display to users.

Reduce polling

If your software stores a local copy of data retrieved from Tracker and/or operates in response to changes in data, you should reconsider the mechanism(s) that you use to keep your data synchronized with Tracker.

First, if you use webhooks from Tracker (so that we POST to a server part of your application when changes are made in Tracker), you will also have to migrate the parsing of webhook request bodies from v3 to v5.

Second, if your software has (or in any way can have) a server component, and it isn’t currently receiving webhooks, you should seriously consider reorganizing it so that it does. The payload in v5 webhooks (which matches the response bodies from the v5 ..../activity endpoints) is far more comprehensive than the v3 format. v5 activity is explicitly intended to provide all of the information necessary to update a client’s local data cache to match changes made in Tracker.

Third, if you have no alternative but to poll the Tracker API for data, consider loading the desired data once and then polling for activity thereafter instead of repeatedly GETing the full data set. To do this efficiently, you will have to keep track of the version number that Tracker maintains for each project. Each project’s version is incremented when it changes, and the current version number

  1. is available directly in responses that contain information from the project resource; and
  2. is included in the response from all requests that are “scoped” to a particular project_id, either in the HTTP response header X-Tracker-Project-Version or the response envelope when requested. Each response from an activity endpoint is an array of individual activity items, representing discrete changes that have occurred between the project version specified in the request and the moment when the request is processed. Each activity item includes the project’s version number as of the completion of that change. Normaly, the client will retain the highest version from one activity response and use it in the parameters for the next request (on the same project).

Finally, if your client does poll Tracker, you should include a “backoff” mechanism to reduce the polling frequency in the case where it receives error responses from Tracker. In some environments and frameworks, the “natural” way for the client software to operate would be to immediately retry a failed request. This behavior should absolutely be avoided, as it can make your software appear to Tracker as a small denial-of-service attack in response to interruptions in the API’s availability. This could result in your client being rate limited until after it has stopped making high-frequency requests (which could be indefinitely if it doesn’t specifically handle the “you have been rate limited” error response, HTTP status code 429), the API authentication token(s) used by your client being deactivated, or the IP address(es) from which your client executes being blacklisted.

Take advantage of pagination

Most of the v3 API endpoints that returned lists (arrays) of items had a cap on the maximum number that they would return. Most array-returning v5 endpoints also have a maximum number of items that they will return from a single request, but also support a pagination mechanism that allows you to make successive requests with different limit and offset parameters to accumulate the full response set over time.

As with almost all systems that allow fetching partial data sets from a collection that may be asynchronously updated by others, your software should be resilient to the possibility that the length of the set of response items that you are paginating through can change during your sequence of requests. If it is essential that you obtain the exact set of resources maintained by the server, you may wish to make requests with limit/offset values that cause your result sets to overlap, and compare the items in the overlapping portions of successive responses to ensure continuity.

If your existing software GETs arrays of resource items, you should review the matching v5 endpoint for its default limit value and the maximum supported limit to determine if you have to change to pagination in order to get as many items as you were retrieving through v3. You should also consider whether your application would benefit from incorporating pagination so that it can access the full data set, or if a subset remains sufficient. In the case where a subset is sufficient, if it is possible to work with fewer response items than would be provided by the default limit value, you should include an explicit limit in your requests. Requesting with a lower limit than default will provide your client with faster responses by reducing both the time required for Tracker to assemble your response and the size of the response itself.

Finally, review your software to determine if any original API v3 requests can be consolidated into a single request using a new v5 endpoint or response format. In particular, any place that multiple sequential requests were made together unconditionally is a likely candidate for consolidation.

There are two common cases for consolidation: fetching a “large” resource followed by fetching a number of its components, or needing to know something about a “small” resource that (used to) require fetching information about its peers or history.

In the first case, API v5 allows you to explicitly control what attributes and sub-resources of the top-level resource(s) that are returned in an API response will be included in that response and, in cases where a resource contains or is related to other resources, to select whether a relationship is represented by a list of resource ID numbers or by JSON objects containing content from the related resources themselves. For example, the v5/projects endpoint allows all of the iterations, stories, and/or epics within the project to be fetched along with the other project-level information, where some v3-using software immediately followed a GET of the project with separate GET(s) for all the project’s stories.

In the second case, requests can be consolidated because API v5 provides a number of resource attributes that are derived from or summarize the resource’s relationships or history. As examples: The counts attribute of the label resource gives a summary of the stories to which the label currently applies (and therefore the stories currently in an epic, when the label is also associated with one). The v5/iterations/##/analytics/... endpoints provide summary information about stories and story states over time that would require fetching and processing a potentially large number of activity items.

List of equivalent URLs/resources

V3 endpoint conversions

The following is a complete listing of the mapping between deprecated v3 API endpoints and the equivalent v5 API endpoint (where present).

v3 v5
GET https://www.pivotaltracker.com/services/v3/tokens/active GET https://www.pivotaltracker.com/services/v5/me?fields=api_token
POST https://www.pivotaltracker.com/services/v3/tokens/active GET https://www.pivotaltracker.com/services/v5/me?fields=api_token
GET https://www.pivotaltracker.com/services/v3/activities GET https://www.pivotaltracker.com/services/v5/my/activity
GET https://www.pivotaltracker.com/services/v3/projects/##/activities GET https://www.pivotaltracker.com/services/v5/projects/##/activity
GET https://www.pivotaltracker.com/services/v3/projects/## GET https://www.pivotaltracker.com/services/v5/projects/##
GET https://www.pivotaltracker.com/services/v3/projects GET https://www.pivotaltracker.com/services/v5/projects
POST https://www.pivotaltracker.com/services/v3/projects POST https://www.pivotaltracker.com/services/v5/projects
GET https://www.pivotaltracker.com/services/v3/projects/##/memberships GET https://www.pivotaltracker.com/services/v5/projects/##/memberships
GET https://www.pivotaltracker.com/services/v3/projects/##/memberships/## GET https://www.pivotaltracker.com/services/v5/projects/##/memberships/##
POST https://www.pivotaltracker.com/services/v3/projects/##/memberships POST https://www.pivotaltracker.com/services/v5/projects/##/memberships
DELETE https://www.pivotaltracker.com/services/v3/projects/##/memberships/## DELETE https://www.pivotaltracker.com/services/v5/projects/##/memberships/##
GET https://www.pivotaltracker.com/services/v3/projects/##/iterations GET https://www.pivotaltracker.com/services/v5/projects/##/iterations
GET https://www.pivotaltracker.com/services/v3/projects/##/iterations/current_backlog GET https://www.pivotaltracker.com/services/v5/projects/##/iterations?scope=current_backlog
GET https://www.pivotaltracker.com/services/v3/projects/##/iterations/backlog GET https://www.pivotaltracker.com/services/v5/projects/##/iterations?scope=backlog
GET https://www.pivotaltracker.com/services/v3/projects/##/iterations/current GET https://www.pivotaltracker.com/services/v5/projects/##/iterations?scope=current
GET https://www.pivotaltracker.com/services/v3/projects/##/iterations/done GET https://www.pivotaltracker.com/services/v5/projects/##/iterations?scope=done
GET https://www.pivotaltracker.com/services/v3/projects/##/stories/## GET https://www.pivotaltracker.com/services/v5/projects/##/stories/##
GET https://www.pivotaltracker.com/services/v3/projects/##/stories GET https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories
POST https://www.pivotaltracker.com/services/v3/projects/##/stories POST https://www.pivotaltracker.com/services/v5/projects/##/stories
PUT https://www.pivotaltracker.com/services/v3/projects/##/stories/## PUT https://www.pivotaltracker.com/services/v5/projects/##/stories/##
POST https://www.pivotaltracker.com/services/v3/projects/##/stories/##/notes POST https://www.pivotaltracker.com/services/v5/projects/##/stories/##/comments
DELETE https://www.pivotaltracker.com/services/v3/projects/##/stories/## DELETE https://www.pivotaltracker.com/services/v5/projects/##/stories/##
PUT https://www.pivotaltracker.com/services/v3/projects/##/stories/deliver_all_finished No equivalent endpoint
POST https://www.pivotaltracker.com/services/v3/projects/##/stories/##/moves See below for how to use the PUT Story endpoint to move stories
GET https://www.pivotaltracker.com/services/v3/projects/##/stories/##/tasks/## GET https://www.pivotaltracker.com/services/v5/projects/##/stories/##/tasks/##
GET https://www.pivotaltracker.com/services/v3/projects/##/stories/##/tasks GET https://www.pivotaltracker.com/services/v5/projects/##/stories/##/tasks
POST https://www.pivotaltracker.com/services/v3/projects/##/stories/##/tasks POST https://www.pivotaltracker.com/services/v5/projects/##/stories/##/tasks
PUT https://www.pivotaltracker.com/services/v3/projects/##/stories/##/tasks/## PUT https://www.pivotaltracker.com/services/v5/projects/##/stories/##/tasks/##
DELETE https://www.pivotaltracker.com/services/v3/projects/##/stories/##/tasks/## DELETE https://www.pivotaltracker.com/services/v5/projects/##/stories/##/tasks/##
POST https://www.pivotaltracker.com/services/v3/projects/##/stories/##/attachments See the section on attachments below
POST https://www.pivotaltracker.com/services/v3/source_commits POST https://www.pivotaltracker.com/services/v5/source_commits

Selected examples of v3 requests and the v5 replacement

Some of the similarities and differences between v3 and v5 are illustrated in these examples of using selected endpoints in both versions.

Retrieving a user token

In v3, the ‘tokens’ endpoint was used to retrieve a user token via basic auth or POST.

Request: Via Basic Auth:

curl -u $USERNAME:$PASSWORD -X GET https://www.pivotaltracker.com/services/v3/tokens/active

Via POST:

curl -d username=$USERNAME -d password=$PASSWORD -X POST https://www.pivotaltracker.com/services/v3/tokens/active

Response:

<?xml version="1.0" encoding="UTF-8"?>
<token>
  <guid>c93f12c71bec27843c1d84b3bdd547f3</guid>
  <id type="integer">1</id>
</token>

In v5, you can obtain your Tracker API Token using the me endpoint.

Request:

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

Response:

status: 200

                         Headers

Content-Type: application/json; potentially-other-variable-stuff

                         Response body
{
  "api_token": "096276d15e47c834ac84c85d0f8e56bc",
  "id": 745501
}

Retrieving activity

In v3, you could retrieve up to 100 activity entries for a project. It allowed parameters of limit= and occurred_since_date.

Request:

curl -H "X-TrackerToken: $TOKEN" -X GET "https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/activities?limit=100&occurred_since_date=2010/3/15%0000:00:00%20PST"

Response:

<?xml version="1.0" encoding="UTF-8"?>
<activities type="array">
  <activity>
    <id type="integer">1031</id>
    <version type="integer">175</version>
    <event_type>story_update</event_type>
    <occurred_at type="datetime">2010/03/15 04:20:09 PST</occurred_at>
    <author>James Kirk</author>
    <project_id type="integer">26</project_id>
    <description>Beware The Ides of March (and the Klingons)</description>
    <stories type="array">
      <story>
        <id type="integer">109</id>
        <project_id type="integer">1</project_id>
        <url>https://www.pivotaltracker.com/services/v3/projects/26/stories/109</url>
        <accepted_at type="datetime">2009/12/14 22:12:09 UTC</accepted_at>
        <current_state>accepted</current_state>
      </story>
    </stories>
  </activity>
</activities>

The project activity endpoint in v5 lets you retrieve entries for the last six months (this may be more depending on your account’s plan). Its parameters allow you to limit, sort, and select based on date and version. The response is paginated.

Request:

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/activity?limit=1"

Response:

status: 200

                         Headers

Content-Type: application/json; potentially-other-variable-stuff

                         Response Body
[
   {
       "kind": "iteration_update_activity",
       "guid": "99_66",
       "project_version": 66,
       "message": "Wilhuff Tarkin changed iteration 1's length from default to 2 weeks",
       "highlight": "changed",
       "changes":
       [
           {
               "kind": "iteration_override",
               "change_type": "update",
               "number": 1,
               "original_values":
               {
                   "number": 1,
                   "finish": 1501588800000,
                   "team_strength": 1,
                   "length": "default"
               },
               "new_values":
               {
                   "number": 1,
                   "team_strength": 0.9,
                   "length": 2,
                   "finish": 1501588805000
               }
           }
       ],
       "primary_resources":
       [
           {
               "kind": "iteration_override",
               "number": 1
           }
       ],
       "secondary_resources":
       [
       ],
       "project":
       {
           "kind": "project",
           "id": 99,
           "name": "Death Star"
       },
       "performed_by":
       {
           "kind": "person",
           "id": 102,
           "name": "Wilhuff Tarkin",
           "initials": "WT"
       },
       "occurred_at": "2017-08-01T12:00:10Z"
   }
]

The v5 API provides additional endpoints to retrieve activity, including all activity performed by you, and all activity performed on a given story or epic,

Retrieve stories with a filter

With v3, you could include search filters with the request to retrieve stories for a project.

Request:

curl -H "X-TrackerToken: $TOKEN" -X GET \
      "https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/stories?filter=label%3A%22needs%20feedback%22%20type%3Abug"

Response:

<?xml version="1.0" encoding="UTF-8"?>
<stories type="array" count="0" total="0" filter="'label:'needs feedback' type:bug">
  <story>
    <id type="integer">$STORY_ID</id>
    <project_id type="integer">$PROJECT_ID</project_id>
    <story_type>bug</story_type>
    <url>https://www.pivotaltracker.com/story/show/$STORY_ID</url>
    <estimate type="integer">-1</estimate>
    <current_state>started</current_state>
    <description>Now, Scotty!</description>
    <name>More power to shields</name>
    <requested_by>James Kirk</requested_by>
    <owned_by>Montgomery Scott</owned_by>
    <created_at>Oct 16, 2008</created_at>
    <created_at type="datetime">2008/12/10 00:00:00 UTC</created_at>
    <accepted_at type="datetime">2008/12/10 00:00:00 UTC</accepted_at>
    <labels>label 1,label 2,label 3</labels>
  </story>
</stories>

You can do this with the v5 stories endpoint as well. However, you cannot combine search filters with several of the stories endpoint parameters that are available in v5, such as with_state and with_label.

Request:

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories?filter=name:\"reactor\""

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 66
Content-Type: text/html

                    Response Body
{
    "data": [
        {
            "created_at": "2017-08-01T12:00:00Z",
            "current_state": "unscheduled",
            "description": "large leak, very dangerous",
            "id": 565,
            "kind": "story",
            "labels": [
                {
                    "created_at": "2017-08-01T12:00:00Z",
                    "id": 2011,
                    "kind": "label",
                    "name": "mnt",
                    "project_id": 99,
                    "updated_at": "2017-08-01T12:00:00Z"
                }
            ],
            "name": "Reactor leak reported in Detention Block AA-23",
            "owner_ids": [],
            "project_id": 99,
            "requested_by_id": 103,
            "story_type": "feature",
            "updated_at": "2017-08-01T12:00:00Z",
            "url": "https://www.pivotaltracker.com/story/show/565"
        }
    ]

Create a comment

The v3 API used an outdated term ‘notes’ for comments. The following POST adds a comment, but the response refers to it as a note.

Request:

curl -H "X-TrackerToken: $TOKEN" -X POST -H "Content-type: application/xml" \
      -d "<note><text>new note via API</text></note>" \
    https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/stories/$STORY_ID/notes

Response:

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <id type="integer">NOTE_ID</id>
  <text>new note via API</text>
  <author>Spock (young)</author>
  <noted_at type="datetime">2009/01/16 18:53:51 UTC</noted_at>
</note>

The v5 API provides the full range of support for retrieving, creating, updating, and deleting comments. Here’s an example of a POST to create a comment.

Request:

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"

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 67
Content-Type: application/json; potentially-other-variable-stuff

                    Response Body

{
   "created_at": "2017-08-01T12: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": "2017-08-01T12:00:00Z"
}

Upload an attachment

The v3 attachments endpoint uploaded the file attachment and attached it to the specified story in an empty comment.

Request:

 curl -H "X-TrackerToken: $TOKEN" -X POST -F Filedata=@/path/to/file https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/stories/$STORY_ID/attachments

Response:

<?xml version="1.0" encoding="UTF-8">
<attachment>
  <id type="integer">12</id>
  <status>Pending</status>
</attachment>

In v5, the attachment endpoint uploads the file attachment and creates an attachment resource. You’ll use the attachment resource to link the file attachment to a new or existing comment on the story or epic of your choice.

The following example is for putting a single file resource onto a story in a new comment, without any text, on an existing story. This is equivalent to the operation performed by the v3 attachment upload endpoint. The v5 mechanism of creating attachment resources separate from manipulating the comment(s) they are attached to allows for the inclusion of multiple attachments per comment, updating the attachments on an existing comment, creating comments with both text and attachments, and adding attachments to epics—all things that can be done in the Pivotal Tracker UI but were impossible using the v3 API.

Step 1: Upload the file attachment.

Request:

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

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 66
Content-Type: text/html

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

Step 2: Create a comment that includes the file attachment.

Request:

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"file_attachments":[{"id":24,"kind":"file_attachment","filename":"new-imperial-logo.png","size":96228,"width":2000,"height":1995,"uploader_id":101,"thumbnail_url":"/attachments/0000/0024/empire_thumb.png","thumbnailable":true,"uploaded":true,"download_url":"/attachments/0000/0024/empire_big.png","created_at":"2017-08-01T12: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"

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 67
Content-Type: application/json; potentially-other-variable-stuff

                    Response Body
{
   "created_at": "2017-08-01T12: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": "2017-08-01T12:00:00Z"
}

You can update already-existing comments with links to uploaded file attachments using a PUT with the Comments endpoint.

Reprioritize a story

The v3 API provided a Move Stories endpoint to change a story’s priority.

Request:

curl -H "X-TrackerToken: $TOKEN" -X POST \
    "https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/stories/$STORY_ID/moves?move\[move\]=after&move\[target\]=$TARGET_STORY_ID" -d ""

Response:

<?xml version="1.0" encoding="UTF-8"?>
<story>
  <id type="integer">$STORY_ID</id>
  <project_id type="integer">$PROJECT_ID</project_id>
  <story_type>feature</story_type>
  <url>$STORY_URL</url>
  <estimate type="integer">2</estimate>
  <current_state>started</current_state>
  <description></description>
  <name>Make it so</name>
  <requested_by>Picard</requested_by>
  <created_at type="datetime">2008/12/10 00:00:00 UTC</created_at>
</story>

In v5, you can reprioritize or move a story within your project by using the parameters before_id and after_id with the PUT operation for the Story endpoint. For example:

Request:

curl -X PUT -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"after_id":568,"before_id":null,"group":"scheduled","project_id":98}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555"

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 67
Content-Type: application/json; potentially-other-variable-stuff

                    Response Body
{
  "created_at": "2017-08-08T12: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": 98,
   "requested_by_id": 101,
   "story_type": "feature",
   "updated_at": "2017-08-08T12:00:05Z",
   "url": "https://www.pivotaltracker.com/story/show/555"
}

Retrieve iterations

The v3 API specified which groups of iterations to return in the URL. For example, here’s how to retrieve iterations from Current and Backlog:

curl -H "X-TrackerToken: $TOKEN" -X GET https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/iterations/current_backlog

In v5, iterations can be retrieved with optional scope, limit, and offset parameters, and the response is paginated. For example, here’s how to retrieve iterations from Current and Backlog:

curl -X GET -H "X-TrackerToken: $TOKEN" "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/iterations?scope=current_backlog"

Link a story to an external integration tool

In v3, you could link a story to an external tool (such as Lighthouse) by updating a story. For example:

Request:

curl -H "X-TrackerToken: $TOKEN" -X PUT -H "Content-type: application/xml" \
   -d "<story><lighthouse_id>54</lighthouse_id></story>" \
   https://www.pivotaltracker.com/services/v3/projects/$PROJECT_ID/stories/$STORY_ID

Response:

<?xml version="1.0" encoding="UTF-8"?>
<story>
  <id type="integer">$STORY_ID</id>
  <project_id type="integer">$PROJECT_ID</project_id>
  <story_type>chore</story_type>
  <url>https://www.pivotaltracker.com/story/show/$STORY_ID</url>
  <current_state>started</current_state>
  <description>Now, Scotty!</description>
  <name>More power to shields</name>
  <requested_by>James Kirk</requested_by>
  <lighthouse_id>54</lighthouse_id>
  <lighthouse_url>https://mylighthouseapp.com/projects/100/tickets/54</lighthouse_url>
  <integration_id>1</integration_id>
  <created_at type="datetime">2008/12/10 00:00:00 UTC</created_at>
  <updated_at type="datetime">2008/12/14 00:00:00 UTC</updated_at>
  <labels>tribbles,phaser</labels>
</story>

Similarly, in v5 you can use the PUT operation of the Story endpoint to link to an external integration by using the integration id, which you can obtain with the Project Integrations endpoint, and the integration’s specific id of the story, specified with the external_id. For example:

Request:

curl -X PUT -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"integration_id":200,"external_id":"999999999"}' "https://www.pivotaltracker.com/services/v5/projects/$PROJECT_ID/stories/555"

Response:

status: 200

                      Headers
X-Tracker-Project-Version: 67
Content-Type: application/json; potentially-other-variable-stuff

                      Response Body
{
    "created_at": "2017-08-15T23:59:17Z",
    "current_state": "unscheduled",
    "description": "Ignore the droids",
    "external_id": "999999999",
    "id": 555,
    "integration_id": 200,
    "kind": "story",
    "labels": [],
    "name": "Bring me the passengers",
    "owner_ids": [],
    "project_id": 98,
    "requested_by_id": 000,
    "story_type": "feature",
    "updated_at": "2017-08-15T23:59:32Z",
    "url": "https://www.pivotaltracker.com/story/show/555"
}

Source commits

The v3 API supported integration of post-commit hooks of Source Control Management (SCM) systems such as Git. It required a special syntax in the commit message to include one or more story IDs and optionally a state change for the story. For example:

Request:

curl -H "X-TrackerToken: $TOKEN" -X POST -H "Content-type: application/xml" \
  -d "<source_commit><message>$MESSAGE</message><author>$AUTHOR</author><commit_id>$REV</commit_id><url>$URL</url></source_commit>" \
    https://www.pivotaltracker.com/services/v3/source_commits

Response:

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <stories>
    <hash type="array">
      <story>
        <id type="integer">18609</id>
        <project_id type="integer">101</project_id>
        <story_type>feature</story_type>
        <url>https://www.pivotaltracker.com/story/show/555</url>
        <estimate type="integer">1</estimate>
        <current_state>started</current_state>
        <description>Ignore the droids</description>
        <name>bring me the passengers</name>
        <requested_by>Catherine Weaver</requested_by>
        <owned_by>Catherine Weaver</owned_by>
        <created_at type="datetime">2017/08/17 14:23:20 PDT</created_at>
        <updated_at type="datetime">2017/08/17 14:23:21 PDT</updated_at>
        <notes type="array">
          <note>
            <id type="integer">34625</id>
            <text>Commit by CW
someurl  

[#555] some commit</text>
            <author>Catherine Weaver</author>
            <noted_at type="datetime">2017/08/17 14:25:16 PDT</noted_at>
          </note>
        </notes>
      </story>
    </hash>
  </stories>
</hash>

In v5, the Source Commits endpoint specifies similar parameters in the JSON request body. For example:

Request:

curl -X POST -H "X-TrackerToken: $TOKEN" -H "Content-Type: application/json" -d '{"source_commit":{"commit_id":"abc123","message":"[#555] some commit","url":"https://example.com/abc123","author":"Darth Vader"}}' "https://www.pivotaltracker.com/services/v5/source_commits"

Response:

status: 200

                    Headers
X-Tracker-Project-Version: 67
Content-Type: application/json; potentially-other-variable-stuff

                    Response Body
[
  {
      "kind": "story",
      "id": 555,
      "project_id": 99,
      "name": "Bring me the passengers",
      "description": "ignore the droids",
      "story_type": "feature",
      "current_state": "started",
      "estimate": 2,
      "requested_by_id": 101,
      "owner_ids":
      [
      ],
      "labels":
      [
      ],
      "created_at": "2017-08-08T12:00:00Z",
      "updated_at": "2017-08-08T12:00:05Z",
      "url": "https://www.pivotaltracker.com/story/show/555"
  }
]
Previous
REST API v5