TL;DR
The Heroku API lets you automate deployment, manage apps, configure add-ons, and scale infrastructure using RESTful endpoints. It uses OAuth 2.0 and token-based authentication and enforces a rate limit of 10,000 requests/hour per account. This guide gives you practical steps for authentication, core endpoints, CI/CD integration, and deployment strategies.
Introduction
Heroku powers millions of apps globally. If you're building deployment automation, CI/CD pipelines, or multi-app management tools, Heroku API integration is essential.
Teams managing 10+ Heroku apps typically waste 8-12 hours per week on manual deploys and config changes. Automating with the Heroku API streamlines deployments, dyno scaling, and configuration management.
This guide covers the complete integration process: token authentication, app/dyno management, builds, pipelines, add-ons, and troubleshooting. By the end, you'll have a production-ready Heroku integration.
đź’ˇ Tip: Apidog simplifies API integration testing. Test Heroku endpoints, validate authentication, inspect responses, mock data, and share scenarios.
What Is the Heroku API?
Heroku offers a RESTful Platform API for programmatically managing apps and infrastructure:
- Application creation, config, deletion
- Dyno/process scaling
- Build & release management
- Add-on provisioning/config
- Pipeline & promotion management
- Domain/SSL management
- Log drains & monitoring
- Team/collaborator management
Key Features
| Feature | Description |
|---|---|
| RESTful Design | Standard HTTP methods, JSON responses |
| Token Authentication | Bearer token, OAuth 2.0 support |
| Range Requests | Pagination for large result sets |
| Rate Limiting | 10,000 requests/hour/account |
| Idempotent Creates | Safe retry for writes |
| Gzip Compression | Save bandwidth on responses |
API Architecture Overview
Heroku uses a versioned REST API:
https://api.heroku.com/
It follows the JSON:API spec with consistent resource patterns.
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
- Go to the Heroku website
- Click Sign Up, create an account, and verify your email
- Complete account setup
Step 2: Install Heroku CLI
Install the Heroku CLI:
# 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 the CLI:
heroku login
To retrieve an API token:
# Short-lived token
heroku authorizations:create --short-lived
# Long-lived token (for CI/CD)
heroku authorizations:create --description "CI/CD Pipeline" --expires-in "1 year"
Best Practice: Store tokens in environment variables, never hard-code:
# .env
HEROKU_API_KEY="your_api_key_here"
HEROKU_APP_NAME="your-app-name"
Step 4: Token Authentication
All API calls require these headers:
Authorization: Bearer {api_key}
Accept: application/vnd.heroku+json; version=3
Step 5: Make Your First API Call
Test authentication with curl:
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
Reusable API client (Node.js example):
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
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
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
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
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
const deleteApp = async (appName) => {
await herokuRequest(`/apps/${appName}`, {
method: 'DELETE'
});
console.log(`App ${appName} deleted successfully`);
};
Dyno Management
Scaling Dynos
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
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, demos | $5 |
| basic | Small prod apps | $7 |
| standard-1x | Standard workloads | $25 |
| standard-2x | High-perf apps | $50 |
| performance | Critical production | $250+ |
| private | Enterprise isolation | Custom |
Restarting Dynos
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
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
const dyno = await runCommand('my-app', 'npm run migrate');
console.log(`Dyno started: ${dyno.id}`);
Configuration Variables
Getting Config Vars
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
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 – always use env vars for sensitive data.
- Separate configs per environment – keep staging/production different.
- Rotate secrets regularly – update keys/passwords quarterly.
-
Prefix related vars – e.g.,
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET.
Build and Release Management
Creating a Build
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
const getBuild = async (appName, buildId) => {
const response = await herokuRequest(`/apps/${appName}/builds/${buildId}`);
return response;
};
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
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
const rollback = async (appName, releaseId) => {
const response = await herokuRequest(`/apps/${appName}/releases`, {
method: 'POST',
body: JSON.stringify({
rollback: releaseId
})
});
return response;
};
// Usage
const rollbackRelease = await rollback('my-app', 42);
console.log(`Rolled back to v${rollbackRelease.version}`);
Pipeline Management
Creating a Pipeline
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
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
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
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 | App monitoring |
Listing 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
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
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
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
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:
- Standard Limit: 10,000 requests/hour/account
- Window: Rolling 60 minutes
- Reset: Auto after window
HTTP 429 signals "Too Many Requests".
Implementing Rate Limit Handling
Use exponential backoff for retry:
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
| Header | Description |
|---|---|
RateLimit-Limit |
Max requests/hour |
RateLimit-Remaining |
Requests left in window |
RateLimit-Reset |
Unix timestamp window resets |
Troubleshooting Common Issues
Issue: Authentication Fails with 401
Symptoms: “Invalid credentials” error.
Solutions:
- Verify API key:
heroku authorizations - Check token expiry (long-lived = 1 year)
- Remove whitespace from env var
- Regenerate with
heroku authorizations:create
Issue: App Name Already Taken
Symptoms: “Name is already taken”.
Solutions:
- Use unique names: add team/suffix
- Generate UUID-based:
app-${Date.now()} - Namespace:
team-app-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 returns errors.
Solutions:
- Confirm process type in
Procfile - Check dyno quota
- Ensure app is not suspended
- Review usage:
heroku ps --app=my-app
Issue: Build Fails with Timeout
Symptoms: Build hangs or times out.
Solutions:
- Use correct buildpacks
- Cache dependencies
- Split large builds
- Use pre-built slugs
Issue: Rate Limit Exceeded
Symptoms: HTTP 429 responses.
Solutions:
- Implement request queuing
- Use exponential backoff
- Batch requests
- Monitor rate limit headers
// 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 secret managers (Vault, AWS Secrets Manager)
- [ ] Implement rate limiting and request queuing
- [ ] Add robust error handling
- [ ] Enable API call logging
- [ ] Monitor dyno usage
- [ ] Configure log drains
- [ ] Set up pipeline promotions
- [ ] Enable Automatic Certificate Management
- [ ] Set up backups for databases
Monitoring and Alerting
Track these metrics and alert on thresholds:
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 GitHub deployments:
- Challenge: Manual deploys caused errors/delays
- Solution: GitHub Actions + Heroku API integration
- Result: Zero-downtime, 90% faster releases
Implementation:
- GitHub push triggers workflow
- CI runs tests
- Heroku API creates build from source blob
- Promote through staging → production
- Notify team on result
Multi-Environment Management
A consulting firm manages 50+ client apps:
- Challenge: Manual config sync
- Solution: Central config management via API
- Result: 8 hours/week saved, consistent configs
Key points:
- Sync config vars across environments
- Automate add-on provisioning
- Bulk client onboarding
Auto-Scaling Based on Traffic
E-commerce platform handles spikes:
- Challenge: Manual scaling during sales
- Solution: Load-based auto-scaling with API
- Result: Zero downtime during 10x spikes
Logic:
- Monitor response times
- Scale up if p95 latency >500ms
- Scale down when idle
- Alert on high utilization
Conclusion
The Heroku API gives you full control over platform automation. Key takeaways:
- Use Bearer tokens securely; rotate often
- Monitor rate limits (10K/hour/account)
- Use pipelines for robust CI/CD
- Implement strong error handling for reliability
- Apidog streamlines testing and collaboration
FAQ Section
What is the Heroku API used for?
The Heroku API enables programmatic management of apps, dynos, add-ons, and infra. Use it for CI/CD, multi-app tools, auto-scaling, and monitoring dashboards.
How do I get a Heroku API key?
Install the Heroku CLI, run heroku login, then create an auth with heroku authorizations:create. Store the token securely in environment variables.
Is the Heroku API free to use?
Yes, the API is free, but you pay for resources (dynos, add-ons). Rate limit: 10,000 requests/hour/account.
What authentication does Heroku API use?
Bearer token authentication. Add Authorization: Bearer {api_key} to 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. Use request queuing and exponential backoff on HTTP 429. Stay under 150 req/min for safety.
Can I deploy without Git?
Yes. Use the Builds API to deploy from a source blob URL (e.g., S3, GCS).
How do I automate deployments?
Use the Pipeline API: create builds, promote slugs, and integrate with GitHub/custom CI.
What is the difference between a release and a build?
A build compiles source into a slug. A release combines the slug + config vars to deploy your app.
How do I rollback a failed deployment?
Use the Releases API to list releases, then POST to /releases with rollback: <release_id>.
Can I manage multiple Heroku accounts?
Yes. Use separate API tokens for each account and switch by updating the HEROKU_API_KEY env variable.

Top comments (0)