TL;DR
The Heroku API lets you automate deployments, manage apps, configure add-ons, and scale infrastructure programmatically. It uses OAuth 2.0 and token-based authentication, RESTful endpoints for apps, dynos, builds, and pipelines, and a rate limit of 10,000 requests per hour per account.
This guide shows how to authenticate, call core endpoints, integrate Heroku into CI/CD, and handle production concerns such as retries, rate limits, and rollbacks.
Introduction
Heroku powers over 4 million applications across 170+ countries. If you are building deployment automation, CI/CD pipelines, or multi-app management tools, integrating with the Heroku API is a practical way to replace repetitive CLI and dashboard work.
Teams managing 10+ Heroku apps can spend hours every week on manual deploys and configuration changes. With the API, you can automate deployments, scale dynos based on traffic, and synchronize configuration across environments.
You will implement:
- Bearer-token authentication
- App, dyno, and config-var management
- Build, release, and rollback workflows
- Pipelines and slug promotion
- Add-on, domain, and SSL configuration
- Rate-limit handling and production monitoring
๐ก Apidog can help test Heroku endpoints, validate authentication flows, inspect responses, mock API behavior, and share test scenarios with your team.
What Is the Heroku API?
Heroku provides a RESTful Platform API for managing applications and infrastructure on the Heroku platform.
You can use it to automate:
- Application creation, configuration, and deletion
- Dyno scaling and process management
- Build and release management
- Add-on provisioning and configuration
- Pipeline and promotion management
- Domain and SSL certificate management
- Log drain and monitoring setup
- Team and collaborator management
Key Features
| Feature | Description |
|---|---|
| RESTful design | Standard HTTP methods with JSON responses |
| Token authentication | Bearer token with OAuth 2.0 support |
| Range requests | Pagination for large result sets |
| Rate limiting | 10,000 requests per hour per account |
| Idempotent creates | Safe retry behavior for writes |
| Gzip compression | Response compression for bandwidth savings |
API Architecture Overview
Heroku uses a versioned REST API:
https://api.heroku.com/
The API follows the JSON:API specification with consistent resource patterns and relationships.
API Versions Compared
| Version | Status | Authentication | Use case |
|---|---|---|---|
| Platform API (v3) | Current | Bearer token | New integrations |
| GitHub Integration | Current | OAuth 2.0 | GitHub-connected apps |
| Container Registry | Current | Docker auth | Container deployments |
Getting Started: Authentication Setup
Step 1: Create a Heroku Account
Before accessing the API, create a Heroku account.
- Visit the Heroku website.
- Click Sign Up and create an account.
- Verify your email address.
- Complete account setup.
Step 2: Install the Heroku CLI
Use the CLI to authenticate, generate tokens, and test commands.
# macOS
brew tap heroku/brew && brew install heroku
# Windows
npm install -g heroku
# Linux
curl https://cli-assets.heroku.com/install.sh | sh
Step 3: Generate an API Token
Log in through the CLI:
heroku login
This opens a browser for authentication and stores your credentials locally.
Create a short-lived authorization token:
heroku authorizations:create --short-lived
Or create a long-lived token for CI/CD:
heroku authorizations:create \
--description "CI/CD Pipeline" \
--expires-in "1 year"
Store credentials in environment variables, never in source code:
# .env
HEROKU_API_KEY="your_api_key_here"
HEROKU_APP_NAME="your-app-name"
Step 4: Send Authentication Headers
Heroku uses Bearer token authentication. Include these headers in every request:
Authorization: Bearer {api_key}
Accept: application/vnd.heroku+json; version=3
Step 5: Make Your First API Call
Verify that your token works:
curl -n https://api.heroku.com/account \
-H "Accept: application/vnd.heroku+json; version=3" \
-H "Authorization: Bearer $HEROKU_API_KEY"
Example response:
{
"id": "user-id-here",
"email": "developer@example.com",
"name": "Developer Name",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2026-03-20T14:22:00Z"
}
Step 6: Create a Reusable JavaScript Client
Create one request helper so every call includes the required headers and consistent error handling.
const HEROKU_API_KEY = process.env.HEROKU_API_KEY;
const HEROKU_BASE_URL = "https://api.heroku.com";
const herokuRequest = async (endpoint, options = {}) => {
const response = await fetch(`${HEROKU_BASE_URL}${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${HEROKU_API_KEY}`,
Accept: "application/vnd.heroku+json; version=3",
"Content-Type": "application/json",
...options.headers
}
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(
`Heroku API Error (${response.status}): ${error.message || response.statusText}`
);
}
if (response.status === 204) {
return null;
}
return response.json();
};
// Usage
const account = await herokuRequest("/account");
console.log(`Logged in as: ${account.email}`);
Application Management
Create an App
Create a Heroku app programmatically:
const createApp = async (appName, region = "us") => {
return herokuRequest("/apps", {
method: "POST",
body: JSON.stringify({
name: appName,
region
})
});
};
// Usage
const app = await createApp("my-awesome-app-2026");
console.log(`App created: ${app.name}`);
console.log(`Git URL: ${app.git_url}`);
console.log(`Web URL: ${app.web_url}`);
Expected response:
{
"id": "app-uuid-here",
"name": "my-awesome-app-2026",
"region": { "name": "us" },
"created_at": "2026-03-25T10:00:00Z",
"updated_at": "2026-03-25T10:00:00Z",
"git_url": "https://git.heroku.com/my-awesome-app-2026.git",
"web_url": "https://my-awesome-app-2026.herokuapp.com",
"owner": { "email": "developer@example.com" },
"build_stack": { "name": "heroku-24" }
}
List Apps
Fetch apps available to the authenticated account:
const listApps = async (limit = 50) => {
return herokuRequest(`/apps?limit=${limit}`);
};
const apps = await listApps();
apps.forEach((app) => {
console.log(`${app.name} - ${app.web_url}`);
});
Get App Details
Retrieve details for a specific app:
const getApp = async (appName) => {
return herokuRequest(`/apps/${appName}`);
};
const app = await getApp("my-awesome-app-2026");
console.log(`Stack: ${app.build_stack.name}`);
console.log(`Region: ${app.region.name}`);
Update an App
Use PATCH to update app settings:
const updateApp = async (appName, updates) => {
return herokuRequest(`/apps/${appName}`, {
method: "PATCH",
body: JSON.stringify(updates)
});
};
const updated = await updateApp("old-app-name", {
name: "new-app-name"
});
Delete an App
Delete an app only after confirming the target app name in your deployment tooling.
const deleteApp = async (appName) => {
await herokuRequest(`/apps/${appName}`, {
method: "DELETE"
});
console.log(`App ${appName} deleted successfully`);
};
Dyno Management
Scale Dynos
Scale a process type, such as web or worker:
const scaleDyno = async (appName, processType, quantity) => {
return herokuRequest(`/apps/${appName}/formation/${processType}`, {
method: "PATCH",
body: JSON.stringify({ quantity })
});
};
// Scale web dynos to 3
const processType = "web";
const formation = await scaleDyno("my-app", processType, 3);
console.log(`Scaled to ${formation.quantity} ${processType} dynos`);
Get Dyno Formation
Inspect the current formation before making scaling changes:
const getFormation = async (appName, processType = null) => {
const endpoint = processType
? `/apps/${appName}/formation/${processType}`
: `/apps/${appName}/formation`;
return herokuRequest(endpoint);
};
const formation = await getFormation("my-app");
formation.forEach((proc) => {
console.log(`${proc.type}: ${proc.quantity} dynos (@ ${proc.size})`);
});
Available Dyno Sizes
| Dyno type | Use case | Cost/month |
|---|---|---|
| eco | Hobby projects, demos | $5 |
| basic | Small production apps | $7 |
| standard-1x | Standard workloads | $25 |
| standard-2x | High-performance apps | $50 |
| performance | Production-critical apps | $250+ |
| private | Enterprise isolation | Custom |
Restart Dynos
Restart all dynos, or target a specific process type:
const restartDynos = async (appName, processType = null) => {
const endpoint = processType
? `/apps/${appName}/formation/${processType}`
: `/apps/${appName}/dynos`;
await herokuRequest(endpoint, {
method: "DELETE"
});
console.log(`Dynos restarted for ${appName}`);
};
Run a One-Off Dyno
Use one-off dynos for tasks such as migrations or maintenance scripts:
const runCommand = async (appName, command) => {
return herokuRequest(`/apps/${appName}/dynos`, {
method: "POST",
body: JSON.stringify({
command,
size: "standard-1x"
})
});
};
const dyno = await runCommand("my-app", "npm run migrate");
console.log(`Dyno started: ${dyno.id}`);
Configuration Variables
Get Config Vars
Fetch an app's environment variables:
const getConfigVars = async (appName) => {
return herokuRequest(`/apps/${appName}/config-vars`);
};
const config = await getConfigVars("my-app");
console.log(`DATABASE_URL: ${config.DATABASE_URL}`);
console.log(`NODE_ENV: ${config.NODE_ENV}`);
Set Config Vars
Use PATCH to add or update config vars:
const setConfigVars = async (appName, variables) => {
return herokuRequest(`/apps/${appName}/config-vars`, {
method: "PATCH",
body: JSON.stringify(variables)
});
};
const updated = await setConfigVars("my-app", {
NODE_ENV: "production",
API_SECRET: "your-secret-key",
LOG_LEVEL: "info"
});
Config Var Practices
- Never commit secrets: keep sensitive values in environment variables or a secret manager.
- Separate environments: use distinct values for development, staging, and production.
- Rotate credentials regularly: update API keys and passwords on a schedule.
-
Prefix related values: for example,
STRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRET.
Build and Release Management
Create a Build
Use the Builds API to deploy code from a source blob URL:
const createBuild = async (appName, sourceBlobUrl) => {
return herokuRequest(`/apps/${appName}/builds`, {
method: "POST",
body: JSON.stringify({
source_blob: {
url: sourceBlobUrl
}
})
});
};
const build = await createBuild(
"my-app",
"https://storage.example.com/source.tar.gz"
);
console.log(`Build started: ${build.id}`);
console.log(`Status: ${build.status}`);
Poll Build Status
Wait for a build to finish before promoting or releasing it:
const getBuild = async (appName, buildId) => {
return herokuRequest(`/apps/${appName}/builds/${buildId}`);
};
const checkBuildStatus = async (appName, buildId, maxAttempts = 30) => {
for (let i = 0; i < maxAttempts; i += 1) {
const build = await getBuild(appName, buildId);
if (build.status === "succeeded") {
console.log("Build succeeded!");
return build;
}
if (build.status === "failed") {
throw new Error(`Build failed: ${build.output}`);
}
console.log(`Build in progress... attempt ${i + 1}`);
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Build timeout");
};
List Releases
Inspect recent release history:
const listReleases = async (appName, limit = 10) => {
return herokuRequest(`/apps/${appName}/releases?limit=${limit}`);
};
const releases = await listReleases("my-app");
releases.forEach((release) => {
console.log(
`v${release.version} - ${release.description} - ${release.created_at}`
);
});
Roll Back a Release
Create a new release that rolls back to a previous release version:
const rollback = async (appName, releaseId) => {
return herokuRequest(`/apps/${appName}/releases`, {
method: "POST",
body: JSON.stringify({
rollback: releaseId
})
});
};
const rollbackRelease = await rollback("my-app", 42);
console.log(`Rolled back to v${rollbackRelease.version}`);
Pipeline Management
Create a Pipeline
Create a pipeline for your CI/CD workflow:
const createPipeline = async (pipelineName) => {
return herokuRequest("/pipelines", {
method: "POST",
body: JSON.stringify({
name: pipelineName
})
});
};
const pipeline = await createPipeline("my-app-pipeline");
console.log(`Pipeline created: ${pipeline.id}`);
Add Apps to Pipeline Stages
Attach development, staging, and production apps:
const addAppToPipeline = async (pipelineId, appName, stage) => {
return herokuRequest("/pipeline-couplings", {
method: "POST",
body: JSON.stringify({
pipeline: pipelineId,
app: appName,
stage // development, staging, or production
})
});
};
await addAppToPipeline(pipeline.id, "my-app-dev", "development");
await addAppToPipeline(pipeline.id, "my-app-staging", "staging");
await addAppToPipeline(pipeline.id, "my-app-prod", "production");
Promote a Slug
Promote a slug from one app to the next stage:
const promoteSlug = async (slugId, fromApp, toApp) => {
await herokuRequest("/promotions", {
method: "POST",
body: JSON.stringify({
from: fromApp,
to: toApp,
slug: slugId
})
});
console.log(`Promoted slug ${slugId} from ${fromApp} to ${toApp}`);
};
Add-On Management
Provision an Add-On
Provision add-ons through the API:
const provisionAddon = async (appName, addonPlan, config = {}) => {
return herokuRequest("/addon-attachments", {
method: "POST",
body: JSON.stringify({
app: appName,
plan: addonPlan,
config
})
});
};
const db = await provisionAddon("my-app", "heroku-postgresql:mini");
console.log(`Database provisioned: ${db.addon.name}`);
console.log(`DATABASE_URL: ${db.addon.config_vars.DATABASE_URL}`);
Popular Add-Ons
| Add-on | Plan | Starting price | Use case |
|---|---|---|---|
| heroku-postgresql | mini | $5/mo | Production database |
| heroku-redis | mini | $5/mo | Caching, sessions |
| papertrail | choklad | $7/mo | Log aggregation |
| sentry | developer | Free | Error tracking |
| mailgun | sandbox | Free | Email delivery |
| newrelic | lite | Free | Application monitoring |
List Installed Add-Ons
const listAddons = async (appName) => {
return herokuRequest(`/apps/${appName}/addons`);
};
const addons = await listAddons("my-app");
addons.forEach((addon) => {
console.log(
`${addon.plan.name} - $${addon.pricing.plan.price} - ${addon.state}`
);
});
Remove an Add-On
const removeAddon = async (appName, addonId) => {
await herokuRequest(`/apps/${appName}/addons/${addonId}`, {
method: "DELETE"
});
console.log(`Addon ${addonId} removed from ${appName}`);
};
Domain and SSL Management
Add a Custom Domain
const addDomain = async (appName, domainName) => {
return herokuRequest(`/apps/${appName}/domains`, {
method: "POST",
body: JSON.stringify({
hostname: domainName
})
});
};
const domain = await addDomain("my-app", "api.example.com");
console.log(`CNAME target: ${domain.cname}`);
Configure an SSL Certificate
const addSslCertificate = async (
appName,
domainId,
certificateChain,
privateKey
) => {
return herokuRequest(
`/apps/${appName}/domains/${domainId}/ssl_endpoint`,
{
method: "PATCH",
body: JSON.stringify({
ssl_cert: {
cert_chain: certificateChain,
private_key: privateKey
}
})
}
);
};
Enable Automatic Certificate Management
const enableACM = async (appName, domainName) => {
return herokuRequest(
`/apps/${appName}/domains/${domainName}/sni_endpoint`,
{
method: "POST",
body: JSON.stringify({
kind: "acm"
})
}
);
};
Rate Limiting and Quotas
Understand the Limits
Heroku enforces API limits to protect platform stability:
- Standard limit: 10,000 requests per hour per account
- Window: rolling 60-minute window
- Reset: automatic after the window passes
-
Exceeded limit: HTTP
429 Too Many Requests
Read Rate-Limit Headers
Heroku includes rate-limit information in responses:
| Header | Description |
|---|---|
RateLimit-Limit |
Maximum requests per hour |
RateLimit-Remaining |
Remaining requests in the current window |
RateLimit-Reset |
Unix timestamp when the window resets |
To inspect headers, return both parsed data and the original response from your request helper:
const herokuRequestWithMeta = async (endpoint, options = {}) => {
const response = await fetch(`${HEROKU_BASE_URL}${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${HEROKU_API_KEY}`,
Accept: "application/vnd.heroku+json; version=3",
"Content-Type": "application/json",
...options.headers
}
});
const data = response.status === 204 ? null : await response.json();
if (!response.ok) {
throw new Error(
`Heroku API Error (${response.status}): ${data?.message || response.statusText}`
);
}
return { data, headers: response.headers };
};
Implement Exponential Backoff
Retry rate-limited calls with exponential backoff:
const makeRateLimitedRequest = async (
endpoint,
options = {},
maxRetries = 3
) => {
for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
try {
const { data, headers } = await herokuRequestWithMeta(endpoint, options);
const remaining = Number(headers.get("RateLimit-Remaining"));
const resetTime = headers.get("RateLimit-Reset");
if (remaining < 100) {
console.warn(
`Low quota remaining: ${remaining}, resets at ${resetTime}`
);
}
return data;
} catch (error) {
const isRateLimited = error.message.includes("(429)");
if (isRateLimited && attempt < maxRetries) {
const delay = 2 ** attempt * 1000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
};
Troubleshooting Common Issues
Authentication Fails With 401
Symptoms: You receive Invalid credentials errors.
Checks:
- Verify the API key with
heroku authorizations. - Confirm the token has not expired. Long-lived tokens can last up to one year.
- Check for extra whitespace in
HEROKU_API_KEY. - Regenerate the token if necessary:
heroku authorizations:create
App Name Already Taken
Symptoms: You receive a โname is already takenโ error.
Fixes:
- Use globally unique names.
- Add a team, environment, or random suffix.
- Generate names from a timestamp and random value.
const generateUniqueAppName = (baseName) => {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 6);
return `${baseName}-${timestamp}-${random}`;
};
Dyno Formation Fails
Symptoms: Scaling operations return errors.
Checks:
- Verify the process type exists in the app's
Procfile. - Confirm the account has available dyno quota.
- Ensure the app is not suspended.
- Review dyno usage:
heroku ps --app=my-app
Build Times Out
Symptoms: Builds hang or time out after 30 minutes.
Possible fixes:
- Optimize buildpack selection for your language.
- Cache dependencies correctly.
- Split large builds into smaller deployments.
- Use pre-built slugs for faster deployment.
Rate Limit Exceeded
Symptoms: You receive HTTP 429 responses.
Fixes:
- Queue requests.
- Use exponential backoff.
- Batch requests where possible.
- Monitor rate-limit headers before reaching the limit.
A simple queue-based limiter:
class HerokuRateLimiter {
constructor(requestsPerMinute = 150) {
this.queue = [];
this.interval = 60000 / requestsPerMinute;
this.processing = false;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) {
return;
}
this.processing = true;
while (this.queue.length > 0) {
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
if (this.queue.length > 0) {
await new Promise((resolve) => setTimeout(resolve, this.interval));
}
}
this.processing = false;
}
}
Production Deployment Checklist
Before going live:
- [ ] Use long-lived API tokens for CI/CD.
- [ ] Store tokens in secure secret management, such as Vault or AWS Secrets Manager.
- [ ] Implement rate limiting and request queuing.
- [ ] Add comprehensive error handling.
- [ ] Log API calls and failures.
- [ ] Monitor dyno-hour usage.
- [ ] Configure log drains for external logging.
- [ ] Set up pipeline promotions for CI/CD.
- [ ] Enable Automatic Certificate Management.
- [ ] Configure a database backup strategy.
Monitoring and Alerting
Track API, deployment, and dyno usage metrics:
const metrics = {
apiCalls: {
total: 0,
successful: 0,
failed: 0,
rateLimited: 0
},
dynoHours: {
used: 0,
quota: 1000
},
deployments: {
successful: 0,
failed: 0,
avg_duration: 0
}
};
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) {
sendAlert("Heroku API failure rate above 5%");
}
if (metrics.dynoHours.used > metrics.dynoHours.quota * 0.8) {
sendAlert("Dyno hour usage above 80%");
}
Real-World Use Cases
Automated CI/CD Pipeline
A SaaS team automates deployments from GitHub.
- Challenge: Manual deployments caused errors and delays.
- Solution: GitHub Actions with a Heroku API integration.
- Result: Zero-downtime deployments and 90% faster releases.
Implementation flow:
- A GitHub push triggers a workflow.
- Tests run in CI.
- The Heroku API creates a build from a source blob.
- The slug is promoted from staging to production.
- The team receives a success or failure notification.
Multi-Environment Management
A consulting firm manages 50+ client apps.
- Challenge: Manually synchronizing configuration across environments.
- Solution: Central config management using the Heroku API.
- Result: Consistent configuration and 8 hours per week saved.
Key integration points:
- Sync config vars across development, staging, and production.
- Provision add-ons automatically.
- Run bulk operations during client onboarding.
Auto-Scaling Based on Traffic
An e-commerce platform handles traffic spikes.
- Challenge: Manual scaling during sales events.
- Solution: Load-based auto-scaling through the Heroku API.
- Result: Zero downtime during 10x traffic spikes.
Auto-scaling logic:
- Monitor response times through a metrics API.
- Scale up when p95 latency exceeds 500 ms.
- Scale down during low-traffic periods.
- Alert on sustained high utilization.
Conclusion
The Heroku API provides programmatic access to core platform functionality. For a production integration:
- Store and rotate Bearer tokens securely.
- Monitor the 10,000-requests-per-hour limit.
- Use pipelines to build reliable CI/CD flows.
- Handle failures, retries, and rollbacks explicitly.
- Test authentication, request payloads, and error responses before deploying automation.
FAQ
What is the Heroku API used for?
The Heroku API enables programmatic management of applications, dynos, add-ons, and infrastructure. Common use cases include CI/CD automation, multi-app management tools, auto-scaling systems, and infrastructure monitoring dashboards.
How do I get a Heroku API key?
Install the Heroku CLI, run heroku login, then create an authorization:
heroku authorizations:create
Store the returned token securely in environment variables.
Is the Heroku API free to use?
Yes, the Heroku API is free. You pay for provisioned resources such as dynos and add-ons. API rate limits are 10,000 requests per hour per account.
What authentication does the Heroku API use?
Heroku uses Bearer token authentication. Include this header in every request:
Authorization: Bearer {api_key}
Tokens can be short-lived for one hour or long-lived for up to one year.
How do I handle Heroku API rate limits?
Monitor the RateLimit-Remaining header, queue requests, and use exponential backoff after HTTP 429 responses. Staying under 150 requests per minute is a safe operating target.
Can I deploy without Git?
Yes. Use the Builds API to deploy from a source blob URL. Upload code to cloud storage such as S3 or GCS, then reference that URL in the build request.
How do I automate deployments?
Use the Pipeline API to build CI/CD workflows. Create builds, promote slugs through stages, and integrate the flow with GitHub or a custom CI system.
What is the difference between a release and a build?
A build compiles source code into a slug. A release combines a slug with configuration variables to create a deployable version of an app.
How do I roll back a failed deployment?
List recent releases, then send a POST request to /releases with rollback: <release_id>. Heroku creates a new release using the previous version.
Can I manage multiple Heroku accounts?
Yes. Use separate API tokens for each account and switch accounts by changing the HEROKU_API_KEY environment variable.

Top comments (0)