Migrating to V3 of the Add-on Partner API
Last updated April 01, 2024
If you built an add-on before May 2018 (the date we made Add-on Partner API V3 the default version) and haven’t migrated previously, your add-on integration still uses our legacy Add-on Partner API V1. We will be removing support for V1 no later than July 3, 2023. Follow this guide to migrate your add-on to V3. If there are any changes to the deprecation timeline, we’ll update this article and announce it via the Heroku Changelog accordingly. For more information on the Add-on Partner API V1 end of life, see our FAQ article.
Steps to upgrade your add-on API integration from V1 to V3
Upgrading from V1 to V3 is a multi-step process that needs to be orchestrated carefully. In this guide, we’ll walk you through the migration process to help you minimize risk, downtime, and engineering effort.
Pull an up to date copy of your add-on manifest file
The add-on manifest is a standard JSON document describing the interface between Heroku and your add-on service implementation. If you don’t have the latest version of your add-on manifest file at hand, follow these steps to retrieve an up to date copy:
- Install the Heroku CLI if you haven’t done so already.
Install the addons-admin plugin:
$ heroku plugins:install addons-admin
Log-in through Heroku CLI with a user account that has access to your company add-ons in our Partner Portal.
$ heroku login heroku: Press any key to open up the browser to login or q to exit: Opening browser to https://cli-auth.heroku.com/auth/cli/browser/... Logging in... done Logged in as developer@logcapture.com
After a successful login, pull a copy of your add-on manifest file:
$ heroku addons:admin:manifest:pull <addon-slug> Fetching add-on manifest for logcapture... done
This will download the addon-manifest.json
file and store it in your current folder.
A manifest file contains secrets and should not be shared publicly or checked into source control.
Example manifest
{
"id": "logcapture",
"name": "Log Capture",
"api": {
"config_vars_prefix": "LOGCAPTURE",
"config_vars": [
"LOGCAPTURE_URL"
],
"password": "20eea05fB9444c18fAe0189603e08baA",
"sso_salt": "1BDe5132457C99b0E0533c8707C1173c",
"regions": ["us"],
"requires": [
"syslog_drain",
"log_input"
],
"production": {
"base_url": "https://logcapture.com/heroku/resources",
"sso_url": "https://logcapture.com/sso/login"
},
"$base": 161253688712354
}
}
Check if migration is required
If you’re unsure whether you have to migrate or not, open the manifest file and look for a version
field under api
. If there’s no field by that key or its value is set to “1”, then your add-on is still using the Legacy Add-on Partner API V1 integration and needs to be migrated.
Even if your version field is set to “3”, it’s important to check if your add-on service implementation has references to Legacy Add-on Partner App Info API endpoints, as we’ll be removing this API at the same time as the Add-on Partner API V1. See Migrating to Platform API for Partners for information on how to replace references to the legacy endpoints with requests to the current API.
Use a staging version of your add-on for the migration process
We strongly advise you test the V1 to V3 upgrade process using your add-on service staging environment before upgrading in production. If you have never created a staging version of your add-on service, follow these steps:
- Deploy a staging instance of your provisioning infrastructure.
Open a copy of your production add-on manifest file in your favorite editor and modify the
id
(the slug),password
,sso_salt
, config vars and URLs fields to match your staging add-on API endpoints and infrastructure.Example manifest for staging add-on version
{ "id": "logcapture-staging", "name": "Log Capture (staging)", "api": { "config_vars_prefix": "LOGCAPTURE_STAGING", "config_vars": [ "LOGCAPTURE_STAGING_URL" ], "password": "6349E6585913eD9a1", "sso_salt": "bE27844db7eCf68ae", "regions": ["us"], "requires": [ "syslog_drain", "log_input" ], "production": { "base_url": "https://logcapture-staging-1234567890ab.herokuapp.com/heroku/resources", "sso_url": "https://logcapture-staging-1234567890ab.herokuapp.com/sso/login" }, "$base": 163008330730686 } }
Check that you have changed the
id
field correctly, as you could interrupt your production add-on infrastructure in the next step if you fail to do so properly.Push the staging add-on manifest file to create the staging version of your add-on in Partner Portal:
$ heroku addons:admin:manifest:push
After pushing the manifest file, the new staging add-on should be available in Partner Portal.
Staging add-on versions don’t require any further configuration because they will be always kept at the alpha release stage. Alpha add-ons only have a free test plan, which allows your Partner Portal Company Users to provision the staging add-on to their apps.
The test
plan has Invite Only availability. If you need to grant provisioning access to a user who is not a Company User, navigate to https://addons-next.heroku.com/addons/your-addon-slug/plans, and create a Plan Pass.
For testing, replicate a set of common add-on provisioning scenarios. You might create a few test apps and provision your staging add-on to these apps. If your app is attachable, you might create a few attachments as well.
Please contact Heroku Support for help configuring your staging add-on with parity to your production add-on.
Start capturing additional attributes during provisioning
The headers and payloads sent from Heroku with a provisioning request are very similar between the legacy and new versions of the integration APIs. However, there are some additional attributes you need to persist, and some existing attributes you should no longer rely on as they have been deprecated.
Example provisioning request for Legacy Add-on Partner API V1 integration
POST /heroku/resources HTTP/1.1
Host: logcapture-staging-1234567890ab.herokuapp.com
Authorization: Basic bG9nY2FwdHVyZS1zdGFnaW5nOjYzNDlFNjU4NTkxM2VEOWExCg==
Content-Type: application/json
Accept: application/json
{
"callback_url": "https://api.heroku.com/vendor/apps/649251da-3453-495b-bad0-6749a18f7e31",
"heroku_id": "app987654321@heroku.com",
"plan": "test",
"region": "amazon-web-services::us-east-1",
"syslog_token": "d.93c3d476-baa7-4aa0-856d-93710516af11",
"log_drain_token": "d.93c3d476-baa7-4aa0-856d-93710516af11",
"logplex_token": "t.516c8640-5792-4274-9365-b2c26c0a427d",
"log_input_url": "https://token:t.516c8640-5792-4274-9365-b2c26c0a427d@1.us.logplex.io/logs",
"options": {
"foo": "bar",
"baz": "true"
}
}
Request headers differ only for the value assigned to the Accept
header, which has a MIME type of application/json
for the Legacy API V1 and application/vnd.heroku-addons+json; version=3
for the current API.
Add-on Partner API V3 adds the following attributes to the payload that you should accept and persist along with the information you were already capturing:
uuid
(required) - the UUID we use in lieu of the deprecatedheroku_id
andprovider_id
values to uniquely identify a resource for Add-on Plan Change and Add-on Deprovision requests, and for creating the SSO login token.name
(optional) - Logical name assigned to the resource being provisioned. It’s meant to act as a customer facing identifier to be used in messages and notifications in place of the more cryptic UUID.oauth_grant:code
andoauth_grant:type
(required) - Used to exchange for a resource scoped OAuth token (described in more detail below).oauth_grant:expires_at
(required) - A timestamp defining when theoauth_grant:code
above expires. You have 5 minutes in which to exchange your grant code.
Example provisioning request for Add-on Partner API V3 integration
POST /heroku/resources HTTP/1.1
Host: logcapture-staging-1234567890ab.herokuapp.com:443
Authorization: Basic bG9nY2FwdHVyZS1zdGFnaW5nOjYzNDlFNjU4NTkxM2VEOWExCg==
Content-Type: application/json
Accept: application/vnd.heroku-addons+json; version=3
{
"callback_url": "https://api.heroku.com/addons/5b449238-b37d-4a6b-9ca1-28d7c864dd15",
"uuid": "5b449238-b37d-4a6b-9ca1-28d7c864dd15",
"name": "pouph-gps-filter",
"plan": "test",
"region": "amazon-web-services::us-east-1",
"syslog_token": "d.93c3d476-baa7-4aa0-856d-93710516af11",
"log_drain_token": "d.93c3d476-baa7-4aa0-856d-93710516af11",
"oauth_grant": {
"code": "2d7e4b11-f51a-413f-abb5-93f149b2742b",
"expires_at": "2021-09-06T09:19:26.19-07:00",
"type": "authorization_code"
},
"log_input_url": "https://token:t.516c8640-5792-4274-9365-b2c26c0a427d@1.us.logplex.io/logs",
"options": {
"foo": "bar",
"baz": "true"
}
}
Add-on Partner API V3 also removes all deprecated attributes from the payload: heroku_id
(in favor of uuid
) and logplex_token
(replaced by log_input_url
).
Implement OAuth token exchange
The grant code received with the provisioning request allows you to obtain the OAuth tokens you will need in order to interact with the Platform API for Partners. The Grant Code Exchange process will return two tokens: an expirable access_token
used as a bearer token for authenticating requests, and a long‑lived refresh_token
required to obtain a fresh access_token
after it has expired. Note that the access_token
only authorizes you to request data about the add-on resource it was created for —you will need to use each add-on resource’s access_token
to get access to its data in the Platform API. All responses will be scoped to the add‑on resource that the access_token
was created for.
The process requires that you send a POST
request to https://id.heroku.com/oauth/token
with the following form-encoded parameter strings as body:
grant_type
: theoauth_grant:type
from the provisioning request.code
: theoauth_grant:code
from the provisioning request.client_secret
: found on the OAuth Credentials section under tab Settings in your add-on’s Partner Portal page.
Client secret location in the Partner Portal
Example grant exchange interaction using curl
$ curl -X POST https://id.heroku.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=2d7e4b11-f51a-413f-abb5-93f149b2742b&client_secret=36f595fb-47a9-4b57-9e9e-18ca14ae65da"
{
"access_token": "HRKU-829571d6-7842-4868-8093-995a8d09a1a4",
"expires_in": 28800,
"refresh_token": "c37b53c0-221f-458a-b5e5-5054902004ec",
"token_type": "Bearer",
"user_id": "415dd4f9-ba7d-45db-a9ac-8aab248af5df",
"session_nonce": null
}
A correct POST
to the grant exchange endpoint will return a 200 - OK
response with the following attributes of interest for your add-on service implementation:
access_token
- Used to access the Platform API for Partners scoped to a single add-on resource. Normally expires every 8 hours, but may expire early in certain circumstances, such as a credential rotation. When a request to the Platform API returns a401 - Unauthorized
response you should attempt to obtain a fresh token through the token refresh process (explained below) and retry the request.refresh_token
: Valid for the lifetime of the add-on and can be exchanged for a newaccess_token
as many times as needed using a valid OAuthclient_secret
.expires_in
: The number of seconds theaccess_token
is valid for.token_type
: The token type is used in theAuthorization
header of requests to the Platform API for Partners.
You might implement this grant exchange process as part of the previous step when capturing additional provisioning attributes. Once the OAuth grant has been exchanged, save the access_token
, refresh_token
and expiration time on the add-on’s resource record on your side immediately. There’s no way to get that information from our API later. If you have lost the refresh token associated with an add-on resource, get in contact with us by creating a support ticket.
Be sure to encrypt the access_token
and refresh_token
values, as well as your client_secret
, when writing them to persistent storage. These secrets should not be plain text at rest, and their decryption keys should not be easily discoverable.
Implement access token refresh
The access_token
is valid for up to 8 hours (28,800 seconds) but may expire early in certain circumstances, such as a credential rotation. The refresh_token
is immutable and valid for the lifetime of the add-on resource and can be exchanged for a new access_token
as many times as needed using your add-on’s OAuth client_secret
.
Similar to the grant exchange, the refresh process requires that you send a POST
request to https://id.heroku.com/oauth/token
with the following form-encoded parameter strings as body:
grant_type
: the type of code being passed, in this case it must be set torefresh_token
.refresh_token
: the immutablerefresh_token
received through the grant code exchange process for the specific add-on resource.client_secret
: found on the OAuth Credentials section under tab Settings in your add-on’s Partner Portal page.
Example access token refresh interaction using curl
$ curl -X POST https://id.heroku.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=c37b53c0-221f-458a-b5e5-5054902004ec&client_secret=36f595fb-47a9-4b57-9e9e-18ca14ae65da"
{
"access_token": "HRKU-9ebed010-233d-4de3-9f2f-fdfeddddd9c7",
"expires_in": 28800,
"refresh_token": "c37b53c0-221f-458a-b5e5-5054902004ec",
"token_type": "Bearer",
"user_id": "415dd4f9-ba7d-45db-a9ac-8aab248af5df",
"session_nonce": null
}
A correct POST
to the access token refresh endpoint will return a 200 - OK
response with the following attributes of interest for your add-on service implementation:
access_token
: The new access token required to authenticate to the Platform API for Partners.expires_in
: The number of seconds the newaccess_token
is valid for.
Once you receive the new access_token
and expiration time you should persist the information on your side by updating your add-on’s resource record.
You should update the access_token
before it gets expired to avoid failures when sending requests to the Platform API. In case you receive a 401 - Unauthorized
response to a request before the access_token
has expired, refresh the token and retry the request.
Backfill the add-on resource uuid
and name
At this point in the migration process, your add-on service implementation will be able to capture additional attributes during provision, but there will be existing add-on resources missing their resource UUID’s, names or OAuth tokens.
To backfill all missing resource UUIDs (and optionally, names), send a GET
request to https://api.heroku.com/vendor/apps
using the Get All Apps endpoint from our Legacy Add-on Partner App Info API. This API uses basic authentication where the credentials are your manifest ID (add-on slug) and password. The response will be a JSON array with an element for each app that has your add-on service provisioned. Each element will have a resource
object with the required uuid
and name
attributes.
Example to get resource UUID’s and names using curl
$ curl https://logcapture-staging:6349E6585913eD9a1@api.heroku.com/vendor/apps
[
{
"callback_url": "https://api.heroku.com/vendor/apps/fc045862-3954-4869-80d3-0a69063f995f",
"heroku_id": "app888888888@heroku.com",
"name": "test-app-1",
"resource": {
"uuid": "fc045862-3954-4869-80d3-0a69063f995f",
"name": "logcapture-staging-silhouetted-45667"
},
"plan": "test",
"provider_id": "provider_id_12345"
},
{
"callback_url": "https://api.heroku.com/vendor/apps/e5d0dc92-0e7d-4720-af67-0ec40198421f",
"heroku_id": "app878787878@heroku.com",
"name": "test-app-2",
"resource": {
"uuid": "e5d0dc92-0e7d-4720-af67-0ec40198421f",
"name": "logcapture-staging-concave-99081"
},
"plan": "test",
"provider_id": "provider_id_67890"
}
]
If your add-on service has more than 4,000 resources provisioned, the endpoint response will be paginated and a HTTP Link
header will be set on the response with URLs pointing to the previous and next page.
Backfill the OAuth access and refresh tokens
You won’t have an OAuth grant code to exchange for access and refresh tokens for add-on resources that were created before migrating your add-on. You can use an endpoint to retrieve missing OAuth grant information. For security reasons, this endpoint is blocked by default - please log a new support ticket to request access. If you plan to test this backfill process, which we strongly suggest, you will need to request access to this endpoint for your staging add-on infrastructure.
Once we have communicated on the ticket that we’ve enabled the endpoint for your add-on, send a request to the OAuth Grant backfill endpoint from our Legacy Add-on Partner App Info API for each add-on resource created prior to migrating. This endpoint will return a JSON object containing an oauth_grant
object which has the same structure as the one sent with provisioning requests. Store the required information and perform a grant code exchange as explained previously to obtain the tokens.
The grant code returned expires after 5 minutes. If you don’t exchange the code for the access_token
and refresh_token
within those five minutes, it will expire and you’ll have to make the request again for a new OAuth grant. After a successful exchange, be sure to persist the tokens to the add-on resource record immediately. This endpoint will return an error if you send a request for an add-on resource whose tokens were already exchanged, even if the OAuth grant code is valid and hasn’t expired.
Example OAuth grant code backfilling using curl
$ curl https://logcapture-staging:6349E6585913eD9a1@api.heroku.com/vendor/resources/fc045862-3954-4869-80d3-0a69063f995f/oauth-grant
{
"oauth_grant": {
"code": "7cb3faf1-42bd-442d-ac96-ce46333a0b10",
"type": "authorization_code",
"expires_at": "2021-09-06T09:19:26.19-07:01"
}
}
Replace your calls to the Legacy Add-on Partner App Info API
See Migrating to Platform API for Partners for details on how to replace your add-on service’s requests to endpoints on the Legacy Add-on Partner App Info API with calls to the new Platform API for Partners.
Replace Add-on Partner Event Notifications
If your add-on leverages Add-on Partner Event Notifications by including deploy_notify
or attach_notify
in the requires array of the add-on service manifest file, then you need to replace this with an implementation of Webhook Events along your Add-on Partner API integration changes. Furthermore, you will need to subscribe every individual add-on resource to the appropriate Webhook event notifications (api:build
or api:addon-attachment
). The Webhook Events feature provides an extended set of events to which your add-on service implementation might subscribe and better capabilities for managing subscriptions and deliveries.
Webhook event subscriptions are created via Platform API for Partners and require a valid scoped access token. You will be able to subscribe to event notifications after finishing backfilling OAuth grants for all your resources. Your add-on integration should subscribe new add-on resources to event notifications as part of the provisioning flow.
Implement Asynchronous Provisioning (optional)
If you have a provisioning process that takes longer than a few seconds, you should implement asynchronous provisioning. Under asynchronous provisioning, you:
- Return a
202 - Accepted
response status code to a provisioning request while storing relevant metadata. - Provision your add-on resource in the background.
- Update config vars for your add-on instance with the appropriate values.
- Mark your add-on as
provisioned
.
The last two steps are accomplished via the Platform API for Partners.
Your add-on can use both asynchronous and synchronous provisioning - here are the prerequisites to mixing them:
- You’ve already implemented the Add-on Partner API V3 integration, and
- The response status codes:
200 - OK
means the add-on instance is available immediately, and you should send config vars in the response.202 - Accepted
means you’re provisioning asynchronously and will update in a separate request with config vars. You’ll also send another request to mark the add-on asprovisioned
and therefore available for use.
Final migration plan
We encourage you to open a support ticket early on in your migration. This will start a dialogue with our Heroku Ecosystem engineering team, and ensure a smooth transition without service disruption to our shared customers. Once you get in contact, we’ll be able to:
- Verify you have a staging add-on integration that is ready for testing and at parity with your production add-on.
- Coordinate work for each migration step:
- Allowing your add-on service to receive OAuth grants when new resources are provisioned,
- Enabling the OAuth Grant backfilling endpoint,
- Enabling asynchronous provisioning, and
- Updating your add-on service API version.
After you’ve tested your migration strategy thoroughly in your staging add-on integration environment, you should be ready to roll out integration with our V3 API at production level, with the assistance of our engineering team.
Below is one possible migration strategy involving minimal downtime:
- Reject all provision, deprovision, and plan change requests during the migration period with a
503 - Service Unavailable
status code. We’ll retry relevant requests for up to 12 hours and display relevant error messages to customers. - Apply the necessary database changes to store additional metadata.
- Run the backfill strategies you’ve devised to backfill UUIDs, OAuth tokens, and other metadata for V1 resources.
- Optionally, create Webhook event subscriptions for each resource if your add-on requires event notifications.
- Modify and publish your add-on manifest with a version of 3. Pushing your manifest with the new version is the trigger that tells us you’re ready to use the V3 API.
- Ensure your background job runner is dispatching jobs for asynchronous provisioning where appropriate.
- Remove the
503 - Service Unavailable
response gateway you put in place to start accepting responses. - Test that provisioning, deprovisioning, and plan changes are working correctly.
An alternate migration strategy involving no downtime might look like:
- Deploy code to accept V3 requests (as identified by the Accept: header) while continuing to accept V1 requests.
- Apply the necessary database changes to store additional metadata.
- Start acquiring OAuth tokens for new resources sent under V3.
- Run the backfill strategies you’ve devised to backfill UUIDs, OAuth tokens, and other metadata for V1 resources.
- Optionally, create Webhook event subscriptions for each resource if your add-on service requires event notifications.
- Modify and publish your add-on manifest with a version of 3. Pushing your manifest with the new version is the trigger that tells us you’re ready to use the V3 API.
- After our integration API has started to send V3 requests, remove V1 specific code from your integration API.
- Test that provisioning, deprovisioning, and plan changes are working correctly.