TL;DR
The Heroku API enables developers to automate deployment, manage applications, configure add-ons, and scale infrastructure programmatically. It uses OAuth 2.0 and token-based authentication, RESTful endpoints for apps, dynos, builds, and pipelines, with rate limits of 10,000 requests per hour per account. This guide covers authentication setup, core endpoints, CI/CD integration, and production deployment strategies.
Introduction
Heroku powers over 4 million applications across 170+ countries. For developers building deployment automation, CI/CD pipelines, or multi-app management tools, Heroku API integration isn’t optional—it’s essential.
Here’s the reality: teams managing 10+ Heroku apps lose 8-12 hours weekly on manual deploys and configuration changes. A solid Heroku API integration automates deployments, scales dynos based on traffic, and synchronizes configuration across environments.
This guide walks through the complete Heroku API integration process. You’ll learn token authentication, app and dyno management, build pipelines, add-on provisioning, and error troubleshooting. By the end, you’ll have a production-ready Heroku integration.
What Is the Heroku API?
Heroku provides a RESTful Platform API for managing applications and infrastructure on the Heroku platform. The API handles:
- 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 structure:
https://api.heroku.com/
The API follows JSON:API specification with consistent resource patterns and relationships.
API Versions Compared
| Version | Status | Authentication | Use Case |
|---|---|---|---|
| Platform API (v3) | Current | Bearer Token | All 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 Your Heroku Account
Before accessing the API, you need a Heroku account:

- Visit the Heroku website
- Click Sign Up and create an account
- Verify your email address
- Complete account setup
Step 2: Install Heroku CLI
The CLI helps generate API 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 API Token
Authenticate with Heroku CLI:
# Login to Heroku
heroku login
# This opens a browser for authentication
# After login, your token is stored locally
Retrieve your API token:
# View your current 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"
Security note: Store tokens in environment variables, never in code:
# .env file
HEROKU_API_KEY="your_api_key_here"
HEROKU_APP_NAME="your-app-name"
Step 4: Understand Token Authentication
Heroku uses Bearer token authentication:
Authorization: Bearer {api_key}
Accept: application/vnd.heroku+json; version=3
Every API request requires these headers.
Step 5: Make Your First API Call
Test your authentication:
curl -n https://api.heroku.com/account \
-H "Accept: application/vnd.heroku+json; version=3" \
-H "Authorization: Bearer $HEROKU_API_KEY"
Expected 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: Implement Authentication in Code
Create a reusable API client:
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();
throw new Error(`Heroku API Error: ${error.message}`);
}
return response.json();
};
// Usage
const account = await herokuRequest('/account');
console.log(`Logged in as: ${account.email}`);
Application Management
Creating a New App
Create a Heroku app programmatically:
const createApp = async (appName, region = 'us') => {
const response = await herokuRequest('/apps', {
method: 'POST',
body: JSON.stringify({
name: appName,
region: region
})
});
return response;
};
// 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 App 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" }
}
Listing Your Apps
Fetch all apps in your account:
const listApps = async (limit = 50) => {
const response = await herokuRequest(`/apps?limit=${limit}`);
return response;
};
// Usage
const apps = await listApps();
apps.forEach(app => {
console.log(`${app.name} - ${app.web_url}`);
});
Getting App Details
Retrieve detailed app information:
const getApp = async (appName) => {
const response = await herokuRequest(`/apps/${appName}`);
return response;
};
// Usage
const app = await getApp('my-awesome-app-2026');
console.log(`Stack: ${app.build_stack.name}`);
console.log(`Region: ${app.region.name}`);
Updating App Configuration
Modify app settings:
const updateApp = async (appName, updates) => {
const response = await herokuRequest(`/apps/${appName}`, {
method: 'PATCH',
body: JSON.stringify(updates)
});
return response;
};
// Usage - change app name
const updated = await updateApp('old-app-name', {
name: 'new-app-name'
});
Deleting an App
Remove an app from your account:
const deleteApp = async (appName) => {
await herokuRequest(`/apps/${appName}`, {
method: 'DELETE'
});
console.log(`App ${appName} deleted successfully`);
};
Dyno Management
Scaling Dynos
Scale your application up or down:
const scaleDyno = async (appName, processType, quantity) => {
const response = await herokuRequest(`/apps/${appName}/formation/${processType}`, {
method: 'PATCH',
body: JSON.stringify({
quantity: quantity
})
});
return response;
};
// Usage - scale web dynos to 3
const formation = await scaleDyno('my-app', 'web', 3);
console.log(`Scaled to ${formation.quantity} ${processType} dynos`);
Getting Dyno Formation
View current dyno configuration:
const getFormation = async (appName, processType = null) => {
const endpoint = processType
? `/apps/${appName}/formation/${processType}`
: `/apps/${appName}/formation`;
const response = await herokuRequest(endpoint);
return response;
};
// Usage
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 |
Restarting Dynos
Restart all dynos for an app:
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}`);
};
Running One-Off Dynos
Execute commands in isolated dynos:
const runCommand = async (appName, command) => {
const response = await herokuRequest(`/apps/${appName}/dynos`, {
method: 'POST',
body: JSON.stringify({
command: command,
size: 'standard-1x'
})
});
return response;
};
// Usage - run database migration
const dyno = await runCommand('my-app', 'npm run migrate');
console.log(`Dyno started: ${dyno.id}`);
Configuration Variables
Getting Config Vars
Retrieve environment variables:
const getConfigVars = async (appName) => {
const response = await herokuRequest(`/apps/${appName}/config-vars`);
return response;
};
// Usage
const config = await getConfigVars('my-app');
console.log(`DATABASE_URL: ${config.DATABASE_URL}`);
console.log(`NODE_ENV: ${config.NODE_ENV}`);
Setting Config Vars
Update environment variables:
const setConfigVars = async (appName, variables) => {
const response = await herokuRequest(`/apps/${appName}/config-vars`, {
method: 'PATCH',
body: JSON.stringify(variables)
});
return response;
};
// Usage
const updated = await setConfigVars('my-app', {
NODE_ENV: 'production',
API_SECRET: 'your-secret-key',
LOG_LEVEL: 'info'
});
Best Practices for Config Vars
- Never commit secrets - Use environment variables for all sensitive data
- Use separate configs per environment - Different vars for staging vs production
- Rotate secrets regularly - Update API keys and passwords quarterly
- Prefix related variables -
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET
Build and Release Management
Creating a Build
Deploy code via the API:
const createBuild = async (appName, sourceBlobUrl) => {
const response = await herokuRequest(`/apps/${appName}/builds`, {
method: 'POST',
body: JSON.stringify({
source_blob: {
url: sourceBlobUrl
}
})
});
return response;
};
// Usage
const build = await createBuild('my-app', 'https://storage.example.com/source.tar.gz');
console.log(`Build started: ${build.id}`);
console.log(`Status: ${build.status}`);
Getting Build Status
Check build progress:
const getBuild = async (appName, buildId) => {
const response = await herokuRequest(`/apps/${appName}/builds/${buildId}`);
return response;
};
// Poll until complete
const checkBuildStatus = async (appName, buildId, maxAttempts = 30) => {
for (let i = 0; i < maxAttempts; i++) {
const build = await getBuild(appName, buildId);
if (build.status === 'succeeded') {
console.log('Build succeeded!');
return build;
} else 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');
};
Listing Releases
View release history:
const listReleases = async (appName, limit = 10) => {
const response = await herokuRequest(`/apps/${appName}/releases?limit=${limit}`);
return response;
};
// Usage
const releases = await listReleases('my-app');
releases.forEach(release => {
console.log(`v${release.version} - ${release.description} - ${release.created_at}`);
});
Rolling Back to Previous Release
Revert to a previous version:
const rollback = async (appName, releaseId) => {
const response = await herokuRequest(`/apps/${appName}/releases`, {
method: 'POST',
body: JSON.stringify({
rollback: releaseId
})
});
return response;
};
// Usage - rollback to version 42
const rollbackRelease = await rollback('my-app', 42);
console.log(`Rolled back to v${rollbackRelease.version}`);
Pipeline Management
Creating a Pipeline
Set up CI/CD pipelines:
const createPipeline = async (pipelineName) => {
const response = await herokuRequest('/pipelines', {
method: 'POST',
body: JSON.stringify({
name: pipelineName
})
});
return response;
};
// Usage
const pipeline = await createPipeline('my-app-pipeline');
console.log(`Pipeline created: ${pipeline.id}`);
Adding Apps to Pipeline
Connect apps to pipeline stages:
const addAppToPipeline = async (pipelineId, appName, stage) => {
const response = await herokuRequest('/pipeline-couplings', {
method: 'POST',
body: JSON.stringify({
pipeline: pipelineId,
app: appName,
stage: stage // 'development', 'staging', 'production'
})
});
return response;
};
// Usage
await addAppToPipeline(pipelineId, 'my-app-dev', 'development');
await addAppToPipeline(pipelineId, 'my-app-staging', 'staging');
await addAppToPipeline(pipelineId, 'my-app-prod', 'production');
Promoting Slug to Next Stage
Move code through pipeline:
const promoteSlug = async (slugId, toApp) => {
await herokuRequest('/promotions', {
method: 'POST',
body: JSON.stringify({
from: toApp, // Source app
to: toApp, // Target app (next stage)
slug: slugId
})
});
console.log(`Promoted slug ${slugId} to ${toApp}`);
};
Add-On Management
Provisioning Add-Ons
Install Heroku add-ons:
const provisionAddon = async (appName, addonPlan, config = {}) => {
const response = await herokuRequest('/addon-attachments', {
method: 'POST',
body: JSON.stringify({
app: appName,
plan: addonPlan,
config: config
})
});
return response;
};
// Usage - provision PostgreSQL
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 |
Listing Add-Ons
View installed add-ons:
const listAddons = async (appName) => {
const response = await herokuRequest(`/apps/${appName}/addons`);
return response;
};
// Usage
const addons = await listAddons('my-app');
addons.forEach(addon => {
console.log(`${addon.plan.name} - $${addon.pricing.plan.price} - ${addon.state}`);
});
Removing Add-Ons
Uninstall add-ons:
const removeAddon = async (appName, addonId) => {
await herokuRequest(`/apps/${appName}/addons/${addonId}`, {
method: 'DELETE'
});
console.log(`Addon ${addonId} removed from ${appName}`);
};
Domain and SSL Management
Adding Custom Domains
Configure custom domains:
const addDomain = async (appName, domainName) => {
const response = await herokuRequest(`/apps/${appName}/domains`, {
method: 'POST',
body: JSON.stringify({
hostname: domainName
})
});
return response;
};
// Usage
const domain = await addDomain('my-app', 'api.example.com');
console.log(`CNAME target: ${domain.cname}`);
Configuring SSL Certificates
Add SSL to custom domains:
const addSslCertificate = async (appName, domainId, certificateChain, privateKey) => {
const response = await herokuRequest(`/apps/${appName}/domains/${domainId}/ssl_endpoint`, {
method: 'PATCH',
body: JSON.stringify({
ssl_cert: {
cert_chain: certificateChain,
private_key: privateKey
}
})
});
return response;
};
Automated SSL with ACM
Enable Automatic Certificate Management:
const enableACM = async (appName, domainName) => {
const response = await herokuRequest(`/apps/${appName}/domains/${domainName}/sni_endpoint`, {
method: 'POST',
body: JSON.stringify({
kind: 'acm'
})
});
return response;
};
Rate Limiting and Quotas
Understanding Rate Limits
Heroku enforces rate limits to protect API stability:
- Standard Limit: 10,000 requests per hour per account
- Window: Rolling 60-minute window
- Reset: Automatic after window passes
Exceeding limits results in HTTP 429 (Too Many Requests) responses.
Implementing Rate Limit Handling
Use exponential backoff for retries:
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await herokuRequest(endpoint, options);
// Check rate limit headers
const remaining = response.headers.get('RateLimit-Remaining');
const resetTime = response.headers.get('RateLimit-Reset');
if (remaining < 100) {
console.warn(`Low quota remaining: ${remaining}, resets at ${resetTime}`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
Rate Limit Headers
Heroku includes these headers in every response:
| Header | Description |
|---|---|
RateLimit-Limit |
Maximum requests per hour |
RateLimit-Remaining |
Remaining requests in window |
RateLimit-Reset |
Unix timestamp when window resets |
Troubleshooting Common Issues
Issue: Authentication Fails with 401
Symptoms: Getting “Invalid credentials” errors.
Solutions:
- Verify API key is correct:
heroku authorizations - Ensure token hasn’t expired (long-lived tokens last 1 year)
- Check for extra whitespace in environment variable
- Regenerate token if needed:
heroku authorizations:create
Issue: App Name Already Taken
Symptoms: Getting “name is already taken” error.
Solutions:
- Use globally unique names - include team or random suffix
- Generate UUID-based names:
app-${Date.now()} - Use namespace prefixes:
teamname-appname-env
const generateUniqueAppName = (baseName) => {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 6);
return `${baseName}-${timestamp}-${random}`;
};
Issue: Dyno Formation Fails
Symptoms: Scaling operations return errors.
Solutions:
- Verify process type exists in Procfile
- Check account has available dyno quota
- Ensure app is not suspended
- Review dyno hour usage:
heroku ps --app=my-app
Issue: Build Fails with Timeout
Symptoms: Builds hang or timeout after 30 minutes.
Solutions:
- Optimize buildpack selection for your language
- Cache dependencies properly
- Split large builds into smaller deployments
- Use pre-built slugs for faster deployment
Issue: Rate Limit Exceeded
Symptoms: Receiving HTTP 429 responses.
Solutions:
- Implement request queuing
- Use exponential backoff for retries
- Batch requests where possible
- Monitor rate limit headers proactively
// Simple rate 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(r => setTimeout(r, 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 (Vault, AWS Secrets Manager)
- [ ] Implement rate limiting and request queuing
- [ ] Add comprehensive error handling
- [ ] Set up logging for all API calls
- [ ] Monitor dyno hour usage
- [ ] Configure log drains for external logging
- [ ] Set up pipeline promotions for CI/CD
- [ ] Enable Automatic Certificate Management
- [ ] Configure backup strategy for databases
Monitoring and Alerting
Track these 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
}
};
// Alert on high failure rate
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) {
sendAlert('Heroku API failure rate above 5%');
}
// Alert on dyno hour usage
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 + Heroku API integration
- Result: Zero-downtime deployments, 90% faster releases
Implementation flow:
- GitHub push triggers workflow
- Tests run in CI
- Heroku API creates build from source blob
- Promote through staging to production
- Notify team on success/failure
Multi-Environment Management
A consulting firm manages 50+ client apps:
- Challenge: Manual config sync across environments
- Solution: Central config management with Heroku API
- Result: Consistent configs, 8 hours/week saved
Key integration points:
- Sync config vars across dev/staging/prod
- Automated add-on provisioning
- Bulk operations for 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 with Heroku API
- Result: Zero downtime during 10x traffic spikes
Auto-scaling logic:
- Monitor response times via metrics API
- Scale up when p95 latency > 500ms
- Scale down during low-traffic periods
- Alert on sustained high utilization
Conclusion
The Heroku API provides comprehensive access to platform functionality. Key takeaways:
- Bearer token authentication requires secure storage and rotation
- Rate limiting (10K/hour) requires proactive monitoring
- Pipeline management enables robust CI/CD workflows
- Proper error handling ensures deployment reliability
- Apidog streamlines API testing and team collaboration for Heroku integrations
FAQ Section
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 with heroku authorizations:create. Store the returned token securely in environment variables.
Is the Heroku API free to use?
Yes, the Heroku API is free. However, you pay for the resources you provision (dynos, add-ons, etc.). API rate limits are 10,000 requests per hour per account.
What authentication does Heroku API use?
Heroku uses Bearer token authentication. Include Authorization: Bearer {api_key} header in every request. Tokens can be short-lived (1 hour) or long-lived (up to 1 year).
How do I handle Heroku API rate limits?
Monitor the RateLimit-Remaining header and implement request queuing. Use exponential backoff when receiving HTTP 429 responses. Stay under 150 requests per minute for safe operation.
Can I deploy without Git?
Yes. Use the Builds API to deploy from a source blob URL. Upload your code to cloud storage (S3, GCS) and reference the URL in your build request.
How do I automate deployments?
Use the Pipeline API to set up CI/CD. Create builds, promote slugs through stages, and integrate with GitHub or custom CI systems.
What is the difference between a release and a build?
A build compiles your source code into a slug. A release combines a slug with configuration variables to create a deployable version of your app.
How do I rollback a failed deployment?
Use the Releases API to list recent releases, then POST to /releases with rollback: <release_id>. Heroku creates a new release at the previous version.
Can I manage multiple Heroku accounts?
Yes. Use separate API tokens for each account and switch between them by changing the HEROKU_API_KEY environment variable.



