Migrate an API key integration to a private app

If you've built an internal integration that uses a HubSpot API key, your API key provides both read and write access to all of your HubSpot CRM data, which can be a security risk if your API key is compromised. By migrating to a private app, you can authorize the specific scopes that your integration requires, which generates an access token that limits the data that your integration can request or change in your account.

In addition, while API keys require token refreshing, private app access tokens do not. Therefore, you won’t need to include token refreshing as a part of your application.

Follow the steps below to migrate an existing API key integration to a private app. It's recommended you first use a test environment, such as a developer test account or sandbox account, before making changes in production. If you have questions while migrating your app, visit the Developer Community

In this guide

Please note: private apps do not support extensions, custom timeline events or webhooks. If your existing integration uses any of these features, you should create a public app using OAuth instead.

Create a new private app

  • In your HubSpot account, click the settings icon in the main navigation bar.
  • In the left sidebar menu, navigate to Integrations > Private Apps.
  • Click Create private app.
  • On the Basic Info tab, configure the details of your app:
    • Enter your app's name.
    • Hover over the placeholder logo and click the upload icon to upload a square image that will serve as the logo for your app.
    • Enter a description for your app.
  • Click the Scopes tab.
  • Next, select the scopes to authorize based on the APIs that your integration uses. To find out which scopes your app will need:
    • Compile a list of HubSpot APIs that your existing integration uses.
    • For each API request, navigate to the associated developer documentation (e.g., the contacts API).
    • Click the Endpoints tab, then scroll to the endpoint your integration is using.
    • Under the Requirements section, locate the scopes required to use the endpoint. Whenever possible, you should opt for scopes listed under Granular scopes instead of the ones under Standard scopes. If no granular scopes are listed, use the standard scopes.locate-scope-in-endpoints-tab-for-private-app-migration
    • Back in the settings for your private app, select the Read or Write checkboxes next to the matching scopes. You can also search for a scope using the Find a scope search bar.select-matching-scope-for-private-app
  • After you're done selecting your scopes, click Create app in the top right. You can always make changes to your app after you create it.
  • In the dialog box, review the info about your app's access token, then click Continue creating.

With your private app created, you can start making API requests using its access token. On the Details tab of the settings page of your private app, click Show token under your access token to reveal it.

show-private-app-access-token-migration-guide

Update the authorization method of your integration's API requests

Instead of using a hapiKey query parameter to make API requests, private app access tokens are included in the Authorization header of your request. When making a request, set the value of the Authorization header to Bearer YOUR_ACCESS_TOKEN.

Your request may resemble the following:

axios.get('https://api.hubapi.com/crm/v3/objects/contacts', { headers: { 'Authorization': `Bearer ${YOUR_ACCESS_TOKEN}`, 'Content-Type': 'application/json' } }, (err, data) => { // Handle the API response } );$headers = [ 'Content-Type: application/json', 'Authorization: Bearer ' . YOUR_ACCESS_TOKEN, ]; $curl = curl_init(); curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl, CURLOPT_URL, 'https://api.hubapi.com/crm/v3/objects/contacts'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $contacts = curl_exec($curl); curl_close($curl); var_dump($contacts);require 'uri' require 'net/http' require 'openssl' url = URI("https://api.hubapi.com/crm/v3/objects/contacts") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) request['content-type'] = 'application/json' token = 'YOUR_ACCESS_TOKEN' request['authorization'] = "Bearer #{token}" response = http.request(request) puts response.read_bodyimport requests url = "https://api.hubapi.com/crm/v3/objects/contacts" headers = { 'content-type': 'application/json', 'authorization': 'Bearer %s' % YOUR_ACCESS_TOKEN } response = requests.request("GET", url, headers=headers) print(response.text)

Private app access tokens are implemented on top of OAuth, so you can also make authenticated calls with your access token using one of HubSpot's client libraries. For example, if you're using the Node.js client library, you can instantiate an OAuth client by passing in your app's access token. 

const hubspotClient = new hubspot.Client({ accessToken: YOUR_ACCESS_TOKEN }); $hubSpot = \HubSpot\Factory::createWithAccessToken('access-token'); $response = $hubSpot->crm()->contacts()->basicApi()->getPage();# Load the gem require 'hubspot-api-client' # Setup client client = Hubspot::Client.new(access_token: 'YOUR_ACCESS_TOKEN') # Get contacts contacts = client.crm.contacts.basic_api.get_pagefrom hubspot import HubSpot api_client = HubSpot(access_token='YOUR_ACCESS_TOKEN') api_client.crm.contacts.get_page()

To complete the migration over to your private app, remove all references to the HubSpot API key from your code, and instead use the approach above to use your private app's access token. Depending on the request you're making, you may want to create a secret to store your token, rather than hard coding it in your requests. Using a secret will prevent your token from being exposed, such as when using a token in a serverless function. To store the access token as a secret:

  • In the terminal, run hs secrets add secretName. It's recommended to name the secret something simple so that you can easily reference it in the future.
  • Paste the access token into the terminal, then press Enter.

You can then access your secret as an environment variable. Learn more in the serverless functions example below.

To confirm that all references to your API key have been removed, you can check the call log in your HubSpot account:

  • In your HubSpot account, click the settings icon in the main navigation bar.
  • In the left sidebar, navigate to Integrations > API key.
  • Review the most recent requests in the Call log tab to confirm that no recent requests have been made since removing all previous references over to use your private app's access token.

check-api-key-call-log-after-migrationOnce you've confirmed all references to your API key have been removed in your code, you can deactivate the key.

Verify requests and monitor logs

Once you've removed all references to the HubSpot API key in your code and replaced them with references to your private app's access token instead, no further code changes are required.

If you followed the steps above in a developer test or sandbox account, repeat the same process in your production account. Then, monitor your private app's API call logs and confirm that none of your app's requests return 400 errors:

  • In your HubSpot account, click the settings icon in the main navigation bar.
  • In the left sidebar menu, navigate to Integrations > Private Apps.
  • Click the name of your private app.
  • Click the Logs tab.
  • Any unsuccessful API request that failed due to a missing scope will appear as a 403 error. If you access the runtime logs of your integration, the response from the corresponding API request should include an error message with details about any missing scopes.

403-error-after-private-app-migration

  • If you need to include a new scope for your private app:
    • Click the Details tab.
    • Click Edit details.
    • At the top of the page, click Scopes.
    • Select the checkbox next to any missing scopes, then click Commit changes in the top right when you're done.

select-missing-scopes-private-app-migration

Learn more about creating and managing private apps, along with their limits, in the private apps guide.

Implementation examples

Below, learn more about common API key usages and how to migrate to private app access tokens instead.

Serverless functions

If you’re using an API key within a serverless function, you can similarly use the private app’s access token for authentication. You'll need to ensure that the private app has the scopes that the function needs to execute. 

To authenticate a serverless function with a private app access token:

  • On the Access token card, click Show token to reveal your access token. Then click Copy to copy the token to your clipboard.
    show-private-app-access-token-1
  • With your access token copied, create a new secret to store the token:
    • In the terminal, run hs secrets add secretName. It's recommended to name the secret something simple so that you can easily reference it in the future.
    • Paste the access token into the terminal, then press Enter.
  • In your serverless function's serverless.json file, add the secret name to the secrets array:
// example serverless.json file { "runtime": "nodejs12.x", "version": "1.0", "secrets": ["secretName"], "endpoints": { "getPrompts": { "method": "GET", "file": "serverlessFunction.js" } } }
  • In your serverless function's JavaScript file, set the value of the Authorization header to Bearer secretName. For example, if you're making a call to the Contacts API using Node.js and axios, the request would look like the following:
// example serverless function const axios = require('axios'); exports.main = (context, sendResponse) => { axios.get(`https://api.hubapi.com/crm/v3/objects/contacts`, { headers: { 'Authorization': `Bearer ${process.env.secretName}`, 'Content-Type': 'application/json' } } ) sendResponse({statusCode: 200}); };

One-time jobs

If you’re using an API key for running one-time jobs, such as creating a property, you can instead create a private app and use its access token to authenticate the call. Once a private app is created, you can reuse its access token for any one-time jobs, as long as the private app has the proper scopes. You can update a private app’s scopes at any time from the private app’s settings in HubSpot. Or, you can delete the private app and create a new one specific to the job you need to run.

private-app-edit-scopes

Create custom objects

Instead of using an API key to create a custom object, you can instead create a private app and use its access token to authenticate the call, as long as the app has the necessary scopes. For example, when using Postman to create a custom object, set the authorization type to Bearer token, then enter the token into the Token field.

postman-private-app-access-token-field

Learn more about creating a custom object using a private app on HubSpot's developer blog.

Custom code workflow actions

If you’re using an API key in a Custom code workflow action, you can instead use the private app’s access token, as long as the app has the necessary scopes. To make the update, open the custom action in the workflow editor, then make the following updates:

const hubspotClient = new hubspot.Client({ accessToken: process.env.secretName });