DigitalOcean Marketplace Vendor License Add-Ons API Documentation

License Add-ons Integration Guide

DigitalOcean License Add-ons empower ISVs to distribute and sell unlocked commercial software through the DigitalOcean marketplace.  

Customers simply select the commercial software they want to run on a droplet and pick a paid plan that meets their needs. After a single click, the droplet is added to their account, with the software installed and unlocked.  Billing is initiated and the licence is invoiced monthly.

Installing licensed software creates two new resources in the customer’s account.  A new droplet runs the base image of the software, and a license add-on activates the software and sets up billing to the customer's account.

As a vendor, you will need to create a droplet 1-click with your software pre-installed and unlocked using one of the available licensing strategies, either by activation code, or IP address binding (see below).  Then you will build an integration between your license server and our License Add-on interface.

Definitions

Before getting started, familiarize yourself with the terminology used for DigitalOcean Marketplace Vendors. You’ll see these throughout the interface and documentation.

Software License = A License to use commercial software, made available through the DigitalOcean Marketplace as a 1-click app.  This license is provisioned via the DO Add-Ons integration with your licensing service (the App).

Entitlement = The act of determining whether or not an instance or customer of the licensed droplet 1-click has access to vendor software, and what features are unlocked.  Entitlement is granted via a License Key or via the IP address of the droplet, and is managed by the Vendor through their “entitlement server.”

Licensed Droplet 1-click = A DigitalOcean 1-click that requires payment and entitlement to use.

Activation Code = An entitlement verification approach where a unique token is injected into the server, unlocking the software and making it available to the customer. Also known as Activate Key, License Key.

IP Binding = An entitlement verification approach that checks that the current IP address of the licensed droplet 1-click is licensed/entitled to run the software.

License Key App (also referred to as App) = The vendor’s licensing application that the DigitalOcean API talks to.  The app either generates a license key for the user or unlocks the droplet IP address, and manages the lifecycle of the entitlement.

Entitlement Server = A service that the vendor's software communicates with to determine if a customer has entitlement to use a license 1-click.

Add-On Listing = The marketplace listing that represents the integration with the Vendor's License Key App.  It enables DO customers to purchase and manage Software Licenses, separate from the droplet based software.

Resource = An instance of a license the user purchases and provisions through the DigitalOcean Marketplace Add-On. A Marketplace resource maps one-to-one with a customer license key.

Vendor = The person or company whose Software is available on the DigitalOcean Marketplace.

Licensing strategies

The DigitalOcean marketplace supports two strategies for unlocking and licensing a customer's 1-click base software image.  Choose the approach that most closely aligns with how your software licensing system works.

In both cases, we expect that you run some kind of activation or license server which determines if a given installed copy of your software is “licensed” or “activated”.  This is sometimes referred to as “entitlement”.  

Activation Code

The activation code approach expects that your software can be unlocked by adding an activation code to the droplet filesystem or directly into your software.

When customers purchase your droplet 1-click and pick a paid plan, we will request a new activation code from your license server and install it into the new droplet as both a dot file and an environment variable.

Your software will look for the presence of this code and upon validating it, would unlock the software accordingly.

IP Binding

The IP Binding approach uses the purchased droplet’s IP address to determine if it is licensed to run your software.

When customers select your droplet 1-click along with a paid plan, we will create a droplet with your software on it and send you the IP address of the new droplet.


Your license server will accept the IP address and add it to a list of licensed servers.  Your software will need to call the license server with its current IP address to determine that it is activated and allowed to run. 


Getting Started

DigitalOcean Marketplace engineers will support your team as they onboard your Licensed Droplet 1-Click and License Add-on in the DigitalOcean Marketplace. To get started, you need a DigitalOcean account to create a Team and later register as a Vendor.  

  1. Create a License Add-on listing in the DO Marketplace Vendor Portal that uses one of the two Licensing Strategies we support (see below).

The first step is to create an add on listing integration.  This defines the integration with your license activation server and also defines plans and prices.

Part of the License Add-on implementation will require engineering lift on your side, as your team integrates with our APIs and Webhooks, allowing customers to receive either an activation code, or unlock via their droplets IP address.

You can also decide whether to sell your license by itself (ie. not installed on a droplet or connected to your software), or whether to only make it available in combination with a running copy of your software on a droplet.

  1. Create a droplet 1-click listing and associated base image that will install the un-licensed software on a droplet

    The droplet 1-click includes your listing in the DO Marketplace catalog and also contains the “base image” of your software.  This should be an unlicensed or “locked” version of your software that can be unlocked when the customer selects a commercial plan.  
  2. The droplet 1-click is built with the Packer image automation tool.

    https://github.com/digitalocean/marketplace-partners

While creating your 1-click, you’ll have an option to connect it to the License Add-on you created in step 1.  Once published, customers will be able to view the 1-click in our catalog, and install it onto a new droplet with a single “click”.

Creating Your Add-On Listing and Integration

Your add-on listing allows you to define the integration with your License Key App, along with a listing that can be used by customers to purchase licenses in a stand-alone fashion.  IE- they can purchase a license without having it deployed to a droplet yet.

When creating your Add-On listing, you’ll want to have both SEO and naming best practices in mind. For instance, Company, Inc. would be the name of the Vendor, and the add-on’s app_slug (app API identifier) would be license-name. Most vendors have multiple add-on listings for different environments, including one for each of their production and staging environments. To continue our example, the add-on for the staging environment may have an app_slug of license-name-staging. For each add-on and Software License offered, you'll list a payment plan and the features that come with it as well as provide information about support and additional resources.

When you create an add-on listing, you'll be given a set of credentials including a password, salt, and client_secret. Store these securely on your end. You will use the password to authenticate API requests coming from DigitalOcean to your app, the salt to authenticate SSO requests, and the client_secret to exchange an authorization_code for an access_token and refresh_token, to be used when making a request back to DigitalOcean's API with the user’s license key.

Authentication

When DigitalOcean makes API requests to your app's endpoints, we will utilize basic auth. The auth header will contain a base64 encoded string consisting of your app's slug and the pre-shared password in the format: slug:password. For example, an app whose slug is acme and password is 1234 will receive requests with the following authorization header: Authorization: Basic YWNtZToxMjM0.

Retries

A request may be sent multiple times. Therefore, if a request includes a uuid, the response needs to be idempotent.

Timeouts

Your app should respond within no more than 30 seconds. If you need more than 30 seconds to handle the provision request, please use the asynchronous provisioning option.

Provision

When a DigitalOcean user installs a License add-on, DigitalOcean will send a resource provisioning request to your app’s registered endpoint:

// POST /digitalocean/resources
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{
  // User selected app slug as provided by the vendor during vendor registration
  "app_slug": "example",

  // User selected license plan slug as provided by the vendor during vendor registration
  "plan_slug": "license-key",

  // DigitalOcean generated UUID for identifying this specific resource
  "uuid": "RESOURCE_UUID",

  // A name for the resource that the user provided during the provisioning flow.
  "resource_name": "RESOURCE_NAME",

  // The callback URL your app will make a request to if you use the asynchronous provisioning option.
  "callback_url": "CALLBACK_URL",

  // Customizable metadata that a DigitalOcean user can set for this specific resource
  "metadata": { "region": "nyc", "foo": "bar" },

  // An obfuscated email pointing to the user’s email address. Anything sent to this email will be
  // forwarded to the user.
  "email": "<uuid>@digitalocean.com",

  // If your license add-on uses IP binding, the IP address of the droplet to unlock will appear here
  "bound_ip_address"=>"droplet.ip.address",

  // DigitalOcean obfuscated ID that will uniquely identify the user's team. This is useful to know
  // when the same DigitalOcean team provisions multiple resources for your Software License.
  "creator_id": "CREATOR_ID",

  // Your app can asynchronously exchange this authorization_code for an access_token and
  // refresh_token upon success. With the access_token, you can modify this specific resource within
  // DigitalOcean.

  "oauth_grant": {
    "type": "authorization_code",
    "code": "AUTHORIZATION_CODE_UUID",
    "expires_at": 1620915831
  }
}

Your app will then respond with the following:

// HTTP 200 or 201
{
  // Required: An immutable value for DigitalOcean to reference this resource within your app.
  // Can be the resource’s UUID from the originating request.
  "id": "RESOURCE_ID",

  // The variables necessary to enable the DigitalOcean user to utilize your app
  // (endpoints, credentials, etc). They will be displayed to the user from the Software License pages
  // on DigitalOcean. The variables will be prefixed with your Add-On’s Configuration Variable
  // Prefix config_vars_prefix. It is recommended that your variables follow the ALL_CAPS naming
  // convention.

  "config": {
    "EXAMPLE_ENDPOINT": "https://do-user:password@api.example.com/v1",
    "EXAMPLE_LICENSE_KEY": "FOO-BAR-1234"
  },

  // Optional
  "message": "A message that will be displayed to the DigitalOcean user upon completion of the request."
}

Your app should use the config section of the response to send back the user’s Activation Key and any other private credentials.

The customer will be able to access this through the Software License UI in the Control Panel. When displayed, the “configuration variable prefix” will be appended to the variable name you supply.

Config variables can also be updated as described below.

In the event that the resource cannot be created, respond with the following:

// HTTP 422
{
  // Optional
  "message": "A message that will be displayed to the DigitalOcean user as to why the resource could not be created."
}

If the provision request was successful, but your app is using the asynchronous provisioning option, respond with the following:

// HTTP 202

Asynchronous Provisioning

If your app needs more than 30 seconds to handle the resource provision request, you may use the asynchronous provisioning option.

The first step is to have your app respond to the resource provision request with the following:

// HTTP 202

Then, follow the instructions for the Authorization Code Exchange. You will need an access_token to asynchronously provision this resource.

Finally, once your app has the access_token for the resource and has completed provisioning, it will make a POST request to the callback_url value provided in the original provision request. Make sure to include the access_token in the Authorization header:

// Content-Type: application/json
// Authorization: Bearer YWNtZToxMjM0

{
  // Required: An immutable value for DigitalOcean to reference this resource within your app.
  // Can be the resource’s UUID from the originating request.
  "id": "RESOURCE_ID",

  // Required: either one of two values — ”PROVISIONED” if the resource provisioning was successful,
  // or “FAILED” if the provisioning failed.
  "state": "PROVISIONED",


  // The variables necessary to enable the DigitalOcean user to utilize your app
  // (endpoints, credentials, etc). They will be displayed to the user from the add-on pages
  // on DigitalOcean. The variables will be prefixed with your add-on’s Configuration Variable
  // Prefix config_vars_prefix. It is recommended that your variables follow the ALL_CAPS naming
  // convention.
  "config": {
    "EXAMPLE_ENDPOINT": "https://do-user:password@api.example.com/v1",
    "EXAMPLE_LICENSE_KEY": "FOO-BAR-1234"
  },

  // Optional
  "message": "A message that will be displayed to the DigitalOcean user upon completion of the request."
}

Deprovision

When a DigitalOcean user requests that a resource be destroyed, DigitalOcean will send the following request to your app’s registered endpoint:

// DELETE /digitalocean/resources/:resource_uuid
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{}

Your app will then respond with the following:

// HTTP 200 or 204

In the event that the resource cannot be found, respond with the following:

// HTTP 410

In the event that the resource cannot be destroyed, respond with the following:

// HTTP 422

{
  // Optional
  "message": "A message that will be displayed to the DigitalOcean user as to why the resource could not be destroyed."
}

Note: License lifecycle and software entitlement are managed by your software and your app.  If a user de-provisions a resource, it is up to you how to prevent the user from continuing to use the Licensed Droplet 1-Click, or attempt to use the defunct license key elsewhere.

Plan Change (Upgrade/Downgrade)

DigitalOcean users can request to change their plan at any time through the DigitalOcean control panel. Plans cannot be changed by Vendors or within the Vendor application. When a DigitalOcean user requests a plan change, DigitalOcean will send the following request to your app’s registered endpoint:

// PUT /digitalocean/resources/:resource_uuid
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{
  "plan_slug": "newly-selected-plan"
}

Your app will then respond with the following:

// HTTP 200

{
  // Optional
  "message": "A message that will be displayed to the DigitalOcean user upon completion of the request."
}

If you do not wish to return a message, respond with the following instead:

// HTTP 204

In the event that the plan cannot be changed, respond with the following:

// HTTP 422

{
  // Optional
  "message": "A message that will be displayed to the DigitalOcean 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

Note: Software entitlement is managed by your software and your app.  If a user upgrades or downgrades their resource’s plan, it is up to you how to alter the user’s access within your software.

Notifications

Occasionally, DigitalOcean will send important notifications regarding your app or its users. These are often sent by email and by POSTing to a notifications endpoint on your app. The types of notifications we send are summarized here, but could be extended in the future. 

Resource Suspension

resources.suspended 

A resource may be suspended if a user is late paying their invoice. DigitalOcean will notify your app when one or more resources have been suspended with a request of type resources.suspended.

// POST /digitalocean/notifications
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{
  "type": "resources.suspended",
  "created_at": 1620915831,
  "payload": {
    "resources_uuids": []
  }
}

Resource Reactivation

resources.reactivated

A resource may be reactivated if a user pays their late invoice. DigitalOcean will notify your app when one or more resources have been reactivated with a request of type resources.reactivated.

// POST /digitalocean/notifications
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{

  "type": "resources.reactivated",
  "created_at": 1620915831,
  "payload": {
    "resources_uuids": []
  }
}

Resource Deprovisioning Failure

resources.deprovisioning.failed

If a user destroys a resource, your app will be sent a deprovisioning request as described above. If your app does not correctly handle the request, we will notify you with a notification of type resources.deprovisioning.failed.

// POST /digitalocean/notifications
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{
  "type": "resources.deprovisioning.failed",
  "created_at": 1620915831,
  "payload": {
    "resources_uuids": []
  }
}

Resource Updated

resources.updated

A user has the option to rename a resource. Upon renaming one of their add-on resources, DigitalOcean will send you a resources.updated request, which includes the full state of the resource.

// POST /digitalocean/notifications
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0


{

  "type": "resources.updated",
  "created_at": 1620915831,
  "payload": {
    "resource": {
      "uuid": "UUID",
      "name": "NAME",
      "state": "STATE",
      "created_at": 1620915831,
      "updated_at": 1620915831
    },
    "plan": {
      "display_name": "NAME",
      "slug": "SLUG",
      "created_at": 1620915831,
      "updated_at" : 1620915831
    }
  }
}

Authorization Code Exchange

When a resource is provisioned, an authorization_code (which expires after 5 minutes) is provided that will need to be exchanged for an access_token and refresh_token. The access_token has a finite lifespan and gives you the ability to modify the variables for a specific resource. The refresh_token is used to retrieve a new access_token once the old one has expired.

Example authorization_code -

"oauth_grant": {
  "type": "authorization_code",
  "code": "AUTHORIZATION_CODE_UUID",
  "expires_at": 1620915831
}

To request a access_token and refresh_token:

// POST /v2/add-ons/oauth/token
// Host: api.digitalocean.com:443
// Content-Type: application/json

{
  // The authorization code provided during the provisioning request
  "code": "AUTHORIZATION_CODE_UUID",

  // Type of code
  "grant_type": "authorization_code",

  // The preshared secret that is associated with your add-on
  "client_secret": "CLIENT_SECRET_UUID"
}

// HTTP 200

{
  // Used to access the DigitalOcean API scoped to a single resource. Normally expires
  // every 8 hours, but may expire early in certain circumstances.
  "access_token": "ACCESS_TOKEN_UUID",

  // Valid for the lifetime of the resource and can be exchanged for a new access_token
  // as many times as needed using a valid OAuth client_secret.
  "refresh_token": "REFRESH_TOKEN_UUID",

  // The number of seconds the access_token is valid for. The refresh_token is used to
  // acquire a new access_token.
  "expires_in": 28800,

  // The token type is used in the Authorization header of requests to the DigitalOcean API
  "token_type": "Bearer"
}

Access Token Refresh

If your apps access_token has expired, you will need to use the refresh_token returned above to request a new access_token, by making the following request:

// POST /v2/add-ons/oauth/token
// Host: api.digitalocean.com:443
// Content-Type: application/json

{
  // Type of code
  "grant_type": "refresh_token",

  // The authorization code provided during the provisioning request
  "refresh_token": "REFRESH_TOKEN_UUID",

  // The preshared secret that is associated with your add-on
  "client_secret": "CLIENT_SECRET_UUID"
}

DigitalOcean will respond with an updated access_token and a new refresh_token:

// HTTP 200

{
  // Used to access the DigitalOcean API scoped to a single resource. Normally expires
  // every 8 hours, but may expire early in certain circumstances.
  "access_token": "ACCESS_TOKEN_UUID",

  // Valid for the lifetime of the resource and can be exchanged for a new set of access_token
  // and refresh_token as many times as needed using a valid OAuth client_secret
  "refresh_token": "REFRESH_TOKEN_UUID",

  // The number of seconds the access_token is valid for. The refresh_token is used to
  // acquire a new access_token.
  "expires_in": 28800,

  // The token type is used in the Authorization header of requests to the DigitalOcean API
  "token_type": "Bearer"
}

Resource Config Update

Your app can create or update config variables that a customer will need to use your Droplet one-click. This is typically License keys, log in details, or other private credentials, but could be used to provide other types of information as well.

The customer will be able to access this through their Add-Ons UI. When displayed, the “configuration variable prefix” will be appended to the variable name you supply.

Using an access_token, your app can add to, or update the config variables for a given resource.

To update the config variables for a given resource send the access token in the Authorization header.

// PATCH /v2/add-ons/resources/:resource_uuid/config
// Host: api.digitalocean.com:443
// Content-Type: application/json
// Authorization: Bearer U1MJe2Gr2cyEyILEhaaol41uL3aDJERj

The body of your app's request should include the variables you wish to update alongside the value for each.

Note: variables can be updated, but cannot be destroyed. It is recommended that your variables follow the ALL_CAPS naming convention.

{
  "config": {
    "EXAMPLE_ENDPOINT": "https://do-user:password@api.example.com/v1",
    "EXAMPLE_FOO": "BAR"
  }
}

Metadata

When creating an Add-On listing, you have the option to add custom form fields that will be incorporated into the sign-up process. The values provided by a user will be stored with the resource as metadata. Metadata are sent to you during resource creation as part of the provisioning request.

Note: All custom fields you add are required and must be completed by the user.

Add custom fields to your Add-On

To add custom fields to your Add-On, you will need to specify the following for each field:

name: The name is unique among all custom fields for a given Add-On. It is also going to be the slug sent back to you as part of the resource provision payload.

display_name: Human readable name of configurable variable. DigitalOcean will use the name in the Add-On sign-up form.

description: A description of the configurable variable

type: The data type of the expected input value. Currently we support boolean and text string inputs.

options: Options are a list of possible responses for a string custom field. If you provide options, the string input will be changed to a select field with corresponding options.

Example -

// In the below example the user will be able to choose a value only from the given options

name: region
display name: Region
description: Select which region your resource will be created in
type: string (generates a select field)
options: nyc, sfo, blr, hyd

// In the below example the user will be presented with a check box
name: tls
display name: TLS Enabled
description: Select if you want tls enabled
type: boolean (generates a checkbox)

// In the below example the user will be presented with a text box to enter the value
name: name
display name: Name
description: Name of the person who is creating the account
type: string (generates a text input field)

Receive metadata in resource creation payload

User-supplied metadata will be included in the resource provision payload. The “metadata” section will include the key-value representation of the metadata, where the key will be the name of the metadata and value will be the user input.

Example payload -

// POST /digitalocean/resources
// Content-Type: application/json
// Authorization: Basic YWNtZToxMjM0

{
  // User selected app slug as provided by the vendor during vendor registration
  "app_slug": "example",

  // User selected plan slug as provided by the vendor during vendor registration
  "plan_slug": "premium",

  // DigitalOcean generated UUID for identifying this specific resource
  "uuid": "RESOURCE_UUID",
  "callback_url": "https://api.digitalocean.com/v2/saas/RESOURCE_UUID",

  // Customizable metadata that a DigitalOcean user can set for this specific Add-On
  "metadata": { "region": "nyc", "tls": true, "name":"Sammy" },

  // An obfuscated email pointing to the user's email address. Anything sent to this
  // email address will be forwarded to the user.
  "email": "<uuid>@digitalocean.com",

  // DigitalOcean generated UUID that will uniquely identify the user. This is useful to
  // know when the same DigitalOcean user provisions multiple resources.
  "creator_uuid": "CREATOR_UUID",

  // You'll need to asynchronously exchange this authorization_code for an access_token and
  // refresh_token upon success. With the access_token, you can modify this specific resource
  // within DigitalOcean.
  "oauth_grant": {
    "type": "authorization_code",
    "code": "AUTHORIZATION_CODE_UUID",
    "expires_at": 1620915831
  }
}

Note: DigitalOcean Terms of Service prohibit you from collecting email addresses through the custom fields/metadata feature.

Abusive Users

If you determine that a user from DigitalOcean is being abusive on your platform, please submit a ticket in the Marketplace Help Center with the following information:

  • The resource_uuid
  • If you have these, include: the DigitalOcean user_id that is associated with the abusive account and the creator_id.
  • A thorough description of the abuse.

Tips for Testing your Integration

Once you have built your integration, test it and make sure everything works as expected. As a reminder, ensure any person testing your Add-On or Droplet One-Click is a member of the Team associated with these listings in Vendor Portal. For more information, review our Team documentation. From the Vendor Portal UI, there are two ways to test resource provisioning:

Testing provisioning via the Preview button

You can find a Preview button at the bottom of your Add-On’s Create or Edit Listing page. When you select the Preview button, you’ll be shown your Add-On’s listing page in preview mode.  From here, you can test provisioning by selecting the Add your-addon button, and verifying the account was provisioned on your service.

Testing provisioning via the Plan Information tab

On the Plan Information tab within your Add-On’s listing, find the Plans section and select the more menu button to the right of the plan you want to test. Selecting “Test Plan” will provision a resource for that specific plan.

Note: Provisioning paid plans in this way will skip our billing logic, so you should never be charged.

Once you have successfully provisioned a resource (and matching license) on your app, you should test:

  • A License key has been created- your service should have created a license key and it will be available in the add-on view in the “Configuration Variables Section”
  • Resource Destruction: Verify that destroying the resource via DigitalOcean removes the corresponding License entitlement in your service.
  • Notifications: You can test notifications by provisioning a resource, and then changing its name using the edit resources settings form.  This will elicit a resources.updated webhook to your service.


Creating your Licensed Droplet One-Click Image and Listing

Begin by following our documentation for creating standard droplet one-clicks, here:  https://github.com/digitalocean/droplet-1-clicks 

How License Keys are injected into droplets

When a customer purchases a Licensed Droplet One-Click, they will receive a License Key from your app that unlocks or otherwise allows them to use your software.  

To support this, your software needs to be able to consume and use the injected license key supplied by DigitalOcean.

License Keys are injected into droplet 1-clicks in two ways -- through the environment, and through a file stored in the root of your droplet named .digitalocean_addon_credentials.  In both cases, the License Key is described using the following format:

APPSLUG_CONFIG_VAR_NAME=put-your-license-key-here

Your software should look in these two places for the existence of the customer's license key and then validate it before granting entitlement to the software.