Addon service provider guidelines
Learn how to list your own cloud-based service in the Addons Marketplace and the API between Addons.io and your service.
Overview
Before getting started, it's important to get familiar with the terminology used by Addons.io.
Definitions
Team
A group of members who work together to manage resources on Addons.io.
All Addons.io resources, including addons and addon services are grouped under a team. This allows multiple members to collaborate on the same set of resources and ensures that all team members have access to the same set of tools and information.
A team also serves as the billing entity for all resources associated with that team.
Addon service provider
A person or company that offers a cloud-based service to be listed in the Addons Marketplace.
Addon service
A cloud-based service that can be integrated into other cloud-based applications to provide additional functionality. Addon services can include databases, caching services, logging tools, monitoring services, and more.
Addon
A specific instance of an addon service that can be installed and configured within a few clicks.
Addons are designed to save developers time and effort by providing pre-configured, easy-to-use cloud-based services that can enhance the functionality of their applications. When a developer adds an Addon to their Addons.io team, they are essentially provisioning and configuring an instance of the underlying addon service. This allows them to use the addon's functionality and access any data or resources associated with the addon service.
Getting started
To get started as quickly as possible you will need the following:
- Create an Addons.io account.
- Create an Addons.io team that can later be associated with your addon service.
- Activate payouts so you can seamlessly receive payouts (the transfer of funds from Addons.io to your bank account).
- Create a new addon service and apply brand, custom fields and pricing. You can also import your existing Heroku addons with a few clicks by clicking the "Import" button in your addon services list page.
- Integrate your cloud service with the Addons.io API by following the instructions bellow.
Integration
Idempotent requests
Addons.io follows the pattern of at least once delivery when making API requests to your service's endpoints.
To safely retry requests without accidentally performing the same operation twice, all POST
and PUT
requests are expected to be idempotent. In other words, the same request may be sent to your service's endpoints multiple times, and the same response should be returned each time.
Request validation
Request payload of API requests to your service's endpoints may contain additional properties not currently documented here, and such requests should be treated as valid.
Authentication
When you create an addon service, you will be given a set of credentials including a Password
, Single sign-on salt
, and OAuth credentials client secret
. It is very important to store these credentials securely for your cloud service to be using it.
When Addons.io makes API requests to your service's endpoints, we will utilize basic authentication. The Authorization header will contain a base64 encoded string consisting of your addon service's slug
and the pre-shared password
in the format: slug:password
. For example, an addon service whose slug is awesome-service
and password is 1234
will receive requests with the following authorization header: Authorization: Basic YXdlc29tZS1zZXJ2aWNlOjEyMzQK
.
Calls from your addon service to the Addons.io API will use an OAuth access_token
. The access_token
, and refresh_token
can be exchanged for an oauth_grant
authorization code
provided in addon provision request.
Note: If these credentials were compromised or you've lost one of them, you can set new values and regenerate the OAuth client secret, but be aware that your scripts or applications using these credentials will need to be updated and existing access tokens will be invalidated.
Provision
Before integrating your service, you should determine whether it supports synchronous or asynchronous provisioning. If your service responds within 30 seconds or less, you can choose synchronous. Asynchronous provisioning should be used if you need more than 30 seconds to process the provision request. As an example, a DB service that creates EC2 instances behind the scenes would use an asynchronous approach.
Synchronous provisioning
When a user installs your addon-service, we will send an addon provisioning request to your addon service’s API Base URL
(e.g. https://awesome-service.com/addonsio/resources
):
// POST <api_base_url>
// Content-Type: application/json
// Accept: application/json
// Authorization: Basic YXdlc29tZS1zZXJ2aWNlOjEyMzQK
{
// The Addons.io generated unique identifier for identifying this specific addon.
"uuid": "01234567-b704-428c-9ce1-47d323fd3959",
// A friendly name for the addon that the user provided during the provisioning flow.
"name": "awesome-service-2023-01-01-575189",
// The plan that the user selected during the provisioning flow.
"plan": "awesome-service-plan",
// Additional options the user provided during the provisioning flow.
"options": {
"region": "amazon-web-services::us-east-1"
},
// The URL to use to callback to when you use the asynchronous provisioning option.
"callback_url": "https://api.addons.io/teams/01234567-8368-4fa7-ad81-d5feb81055db/addons/01234567-b704-428c-9ce1-47d323fd3959",
// A unique token for this addon that can be used to differentiate log streams.
// This will only be provided for addon services that are set to support log streams.
"log_drain_token": "01234567-4da3-a8fc-c0e4a3d2001f",
// The OAuth grant details that can be used to asynchronously exchange it for an access_token and
// refresh_token.
"oauth_grant": {
"code": "01234567-dc36-4d6d-9f74-f635a12b5728",
"expires_at": "2023-01-01T10:11:12Z",
"type": ,"authorization_code"
},
// The Addons.io team identifier that this addon is billed for.
"team_id": "01234567-8368-4fa7-ad81-d5feb81055db",
// The Addons.io team that this addon is billed for.
"team": {
"id": "01234567-8368-4fa7-ad81-d5feb81055db",
"name": "ACME",
"email": "owner@acme.com"
},
// The Addons.io user identifier - which is a member of the team - that created this addon.
"user_id": "01234567-836d-4314-87b3-da8693ab6a78",
// The Addons.io user - which is a member of the team - that created this addon.
"user": {
"id": "01234567-836d-4314-87b3-da8693ab6a78",
"name": "Example user",
"email": "user@example.com"
}
}
Your addon service will then respond with the following:
// HTTP 200 OK or 201 Created
{
// An immutable value that can be used by us to address your resource.
// This can be the addon's `uuid` from the provision request.
"id": "PROVIDER_ID",
// The configuration variables necessary to utilize your service.
// They will be available to the user from the addon page.
// The variables will be prefixed with your addon service's `Configuration variable prefix` value.
// It is recommended that your variables follow the ALL_CAPS naming convention.
"config": {
"AWESOME_SERVICE_URL": "https://user:password@api.awesome-service.com/v1",
"AWESOME_SERVICE_TOKEN": "some secret token"
},
// A URL capable of receiving log stream.
"log_drain_url": "syslog://stream.awesome-service.com:514",
// A message that will be shown to the user in the addon page.
"message": "A message that will be shown to the Addons.io user upon completion of the request."
}
In the event that the resource cannot be created, respond with the following:
// HTTP 422 Unprocessable entity
{
"message": "A message that will be shown to the Addons.io user as to why the resource could not be created."
}
Asynchronous provisioning
If your app needs more than 30 seconds to handle the resource provision request, you may use the asynchronous provisioning option.
Step 1: Persist metadata and issue a response
Persist the metadata included in the provision request so you can fulfill future requests like plan change, deprovision, SSO, and more.
Then, have your app respond to the resource provision request with the following:
// HTTP 202 Accepted
{
// An immutable value that can be used by us to address your resource.
// This can be the addon's `uuid` from the provision request.
"id": "PROVIDER_ID",
// A message that will be shown to the user in the addon page.
"message": "A message that will be shown to the Addons.io user upon completion of the request."
}
Step 2: Exchange OAuth grant for an access token
Exchange the oauth_grant
provided in the provision request payload, for an access_token
that can then be used to update the addon configuration and set the addon state to provisioned
. Read more in the OAuth section.
Step 3: Provision the necessary resources on your end
You can now provision the necessary resources in your infrastructure.
Step 4: Set addon configuration (Optional)
Once you got the access token for the addon and has completed provisioning, you can update the configuration of an addon by making a PATCH request to the callback_url
value provided in the original provision request, suffixed with /config
.
// PATCH /teams/01234567-8368-4fa7-ad81-d5feb81055db/addons/01234567-b704-428c-9ce1-47d323fd3959/config
// Host: api.addons.io:443
// Content-Type: application/json
// Accept: application/json
// Authorization: Bearer 01234567-4a05-4220-b25b-fbeadb50d7d5
{
// The configuration variables necessary to utilize your service in an array of name and value pairs.
// They will be available to the user from the addon page.
// The variables will be prefixed with your addon service's `Configuration variable prefix` value.
// It is recommended that your variables follow the ALL_CAPS naming convention.
"config": [
{
"name": "AWESOME_SERVICE_URL",
"value": "https://user:password@api.awesome-service.com/v1"
}, {
"name": "AWESOME_SERVICE_TOKEN",
"value": "some secret token"
}
],
// A URL capable of receiving log stream.
"log_drain_url": "syslog://stream.awesome-service.com:514"
}
Step 5: Mark the addon state as provisioned
Finally, you must make a POST request to the callback_url
value provided in the original provision request, suffixed with /actions/provision
to set the addon state to provisioned
.
Make sure to include the access token in the Authorization header:
// POST /teams/01234567-8368-4fa7-ad81-d5feb81055db/addons/01234567-b704-428c-9ce1-47d323fd3959/actions/provision
// Host: api.addons.io:443
// Content-Type: application/json
// Accept: application/json
// Authorization: Bearer 01234567-4a05-4220-b25b-fbeadb50d7d5
Response example:
// HTTP 201 Created
OAuth
Grant code exchange
During the provisioning process, an oauth_grant
with an authorization code
is provided (which expires after 5 minutes) that must be exchanged for an access_token
and refresh_token
. The access_token
has a finite lifespan and gives you the ability to authenticate Addons.io API requests. When the old access_token
expires, the refresh_token
can be used to obtain a new one.
To exchange an oauth_grant
authorization code
with an access_token
and refresh_token
:
// POST /oauth/token
// Host: api.addons.io:443
// Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=01234567-dc36-4d6d-9f74-f635a12b5728&client_secret=01234567-251f-444b-a484-667571e5eba6
Parameters:
Name | Description |
---|---|
grant_type |
Specifies the grant type of this token request. Should be authorization_code to exchange a temporary code for an access token. |
code |
The temporary code to exchange for an access token. |
client_secret |
The OAuth client secret to authenticate the client. |
Response example:
// HTTP 200 OK
{
// Access token that can be used for authenticating Addons.io API requests.
// Normally expires every 8 hours, but may expire early in certain circumstances.
"access_token": "01234567-2895-4d76-ba19-7b14ffa4de7f",
// Valid for the lifetime of the addon and can be exchanged for a new access_token.
// as many times as needed using a valid OAuth client_secret.
"refresh_token": "01234567-a6b8-4b00-a72e-99af4eec9c9c",
// The number of seconds the access_token is valid for.
"expires_in": 28800,
// The token type to be used in the Authorization header.
"token_type": "Bearer"
}
Access token refresh
If the access_token
expired, you will need to use the refresh_token
to request a new access_token
, by making the following request:
// POST /oauth/token
// Host: api.addons.io:443
// Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=01234567-a6b8-4b00-a72e-99af4eec9c9c&client_secret=01234567-251f-444b-a484-667571e5eba6
Parameters:
Name | Description |
---|---|
grant_type |
Specifies the grant type of this token request. Should be refresh_token to exchange a refresh token for an access token. |
refresh_token |
The refresh token to exchange for an access token. |
client_secret |
The OAuth client secret to authenticate the client. |
Response example:
// HTTP 200 OK
{
"access_token": "01234567-4a05-4220-b25b-fbeadb50d7d5",
"refresh_token": "01234567-a6b8-4b00-a72e-99af4eec9c9c",
"expires_in": 28800,
"token_type": "Bearer"
}
Deprovision
Your addon service API Base URL
with the addon UUID will receive the following request when an addon is no longer active:
// DELETE <api_base_url>/:UUID
// Content-Type: application/json
// Accept: application/json
// Authorization: Basic YXdlc29tZS1zZXJ2aWNlOjEyMzQK
Your service will then respond with the following:
// HTTP 200 OK or 204
In case there's an error, respond with the following:
// HTTP 410 Gone
In the event that the addon cannot be deprovisioned, respond with the following:
// HTTP 422 Unprocessable entity
{
// Optional
"message": "A message that will be shown to the Addons.io user as to why the addon could not be deprovisioned."
}
When an addon is being deprovisioned, you must perform these steps to complete the process:
- Revoke the OAuth tokens your service exchanged for the addon.
- Remove all customer data within 30 days of receiving the deprovision request.
Plan change
It is possible for users to request a plan change (upgrade or downgrade) at any time. Your addon service API Base URL
with the addon UUID will receive the following request:
// PUT <api_base_url>/:UUID
// Content-Type: application/json
// Accept: application/json
// Authorization: Basic YXdlc29tZS1zZXJ2aWNlOjEyMzQK
{
"plan": "other-awesome-service-plan"
}
Your service will then respond with the following:
// HTTP 200 OK
{
// Optional
"message": "A message that will be shown to the Addons.io user upon completion of the request."
}
In case of an error, respond with the following:
// HTTP 422 Unprocessable entity
{
// Optional
"message": "A message that will be shown to the Addons.io user as to why the plan couldn't be changed."
}
In the event that the resource cannot be found, respond with the following:
// HTTP 404 Not found
Single sign-on (SSO)
Users of your addon can be seamlessly signed into a personalized dashboard for the resources they create with your service.
When a user of your addon wants to sign in from Addons.io to your service's dashboard, we will generate a single sign-on token by combining the salt (a shared secret), timestamp and the addon UUID and make the following request to your addon service's API single sign-on (SSO) URL
:
// POST <sso_url>
// Content-Type: application/x-www-form-urlencoded
resource_id=01234567-b704-428c-9ce1-47d323fd3959&resource_token=TOKEN×tamp=1673658456&email=USER_EMAIL&user_id=USER_UUID
Parameters:
Name | Description |
---|---|
resource_id |
The addon UUID provided in the original provisioning request. |
resource_token |
Hex encoded, SHA1 encoded hash of resource_id:salt:timestamp value. Used for authenticating the SSO request to your service. |
timestamp |
Unix timestamp at which the SSO request was initiated. |
email or user_email |
The email of the user logging in. |
user_id |
The ID of the user logging in. |
Your API will need to validate the request from Addons.io by verifying the resource token. This is done by creating your own hash from resource_id:salt:timestamp
, using the Single sign-on (SSO) salt
as salt
, SHA1 as the message digest algorithm, and then Hex encoding it.
Note: It is up to you to "expire" tokens using the timestamp provided. A best practice would be to reject any tokens with a timestamp older than a minute or two.
If the token you compute matches the one passed in resource_token
, your service should create a session for the user, and then respond with the following, allowing the user to proceed to your dashboard in a logged-in state:
// HTTP 302 Found
// Location: https://awesome-service.com/dashboard
If the token you compute does not match the one passed in resource_token
, respond with an unauthorized code. You can also render a human-readable page, suggesting that they contact support if they believe their request is legitimate:
// HTTP 401 Unauthorized
Ruby/Sinatra example:
require 'openssl'
post "/addonsio/sso" do
ADDONS_IO_SSO_SALT = "set this with the value of the shared salt"
pretoken = params[:resource_id] + ':' + ADDONS_IO_SSO_SALT + ':' + params[:timestamp]
token = OpenSSL::Digest::SHA1.hexdigest(pretoken).to_s
# Compare tokens
halt 401 if token != params[:resource_token]
# Unauthorize if timestamp is older than 2 minutes
halt 401 if params[:timestamp].to_i < (Time.now - 2*60).to_i
# Access authorized - create a session and redirect
session[:resource] = params[:resource_id]
redirect "/dashboard"
end
Testing
Once you have built the integration, test it and make sure everything works as expected before you publish your addon service.
You can find a Install addon service
button at the top of the addon service page to test the provisioning process.
Testing a plan change and deprovision calls, can only take place for provisioned addons.
Note: While your addon service is in Alpha stage, ensure any user testing your addon service is a member of your team.
Publish your addon service
Every addon service progresses through three publication stages. As your add-on meets the requirements for each stage, you can submit a request to progress your service to the next stage, for us to review.
Although an addon service doesn't have to pass through each phase in sequential order, there is a default flow to how addon services are published to the Addons Marketplace:
Alpha
The Alpha phase is the first phase of the publication cycle. An addon service in the Alpha stage is considered to still be under development, and is being built to be ready for Beta phase. It usually lacks the essential features that are required to be ready for a wide audience to use it. They do not appear in the Addons Marketplace or in search results.
An Alpha addon service can only be installed by the team members of the team that owns the addon service.
Beta
An addon service in the Beta stage generally means that the service does not yet meet our quality standards for GA. An example of this is when you need for more information or feedback from customers to validate you addon service solves a specific pain point, when a stricter security validations are required and uptime is not guaranteed.
A Beta addon service is listed publicly in the Addons Marketplace with a Beta
tag and can be installed by any Addons.io customer.
General Availability (GA)
When the addon service reaches the General Availability (GA) stage, the Beta
tag is removed and it is considered to be fully tested with uptime guarantee.
Promote your addon service
Once your addon service is published on the Addons Marketplace, we recommend sharing it on social media and through your channels as much as possible so a wide audience will know they can leverage your service.
We provide additional recommendations to help you with the promotion:
Blog
Consider writing a blog post that demonstrates your new listing on Addons.io, and how it works. Blogs serve as marketing tool that share your perspective.Social media
Leverage social media promotion to find your initial users through targeted ads, content, and interaction. Consider the audience you're trying to reach and post about your new listing on ProductHunt, Reddit, or other relevant communities.