DEV Community

Cover image for How to Use Heroku API: Complete Integration Guide (2026)
Wanda
Wanda

Posted on • Originally published at apidog.com

How to Use Heroku API: Complete Integration Guide (2026)

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.

Try Apidog today


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/
Enter fullscreen mode Exit fullscreen mode

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

  1. Go to the Heroku website
  2. Click Sign Up, create an account, and verify your email
  3. Complete account setup

Heroku Sign Up

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
Enter fullscreen mode Exit fullscreen mode

Step 3: Generate API Token

Authenticate with the CLI:

heroku login
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Best Practice: Store tokens in environment variables, never hard-code:

# .env
HEROKU_API_KEY="your_api_key_here"
HEROKU_APP_NAME="your-app-name"
Enter fullscreen mode Exit fullscreen mode

Step 4: Token Authentication

All API calls require these headers:

Authorization: Bearer {api_key}
Accept: application/vnd.heroku+json; version=3
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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" }
}
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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'
});
Enter fullscreen mode Exit fullscreen mode

Deleting an App

const deleteApp = async (appName) => {
  await herokuRequest(`/apps/${appName}`, {
    method: 'DELETE'
  });

  console.log(`App ${appName} deleted successfully`);
};
Enter fullscreen mode Exit fullscreen mode

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`);
Enter fullscreen mode Exit fullscreen mode

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})`);
});
Enter fullscreen mode Exit fullscreen mode

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}`);
};
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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'
});
Enter fullscreen mode Exit fullscreen mode

Best Practices for Config Vars

  1. Never commit secrets – always use env vars for sensitive data.
  2. Separate configs per environment – keep staging/production different.
  3. Rotate secrets regularly – update keys/passwords quarterly.
  4. 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}`);
Enter fullscreen mode Exit fullscreen mode

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');
};
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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}`);
};
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

Removing Add-Ons

const removeAddon = async (appName, addonId) => {
  await herokuRequest(`/apps/${appName}/addons/${addonId}`, {
    method: 'DELETE'
  });

  console.log(`Addon ${addonId} removed from ${appName}`);
};
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

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:

  1. Verify API key: heroku authorizations
  2. Check token expiry (long-lived = 1 year)
  3. Remove whitespace from env var
  4. Regenerate with heroku authorizations:create

Issue: App Name Already Taken

Symptoms: “Name is already taken”.

Solutions:

  1. Use unique names: add team/suffix
  2. Generate UUID-based: app-${Date.now()}
  3. 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}`;
};
Enter fullscreen mode Exit fullscreen mode

Issue: Dyno Formation Fails

Symptoms: Scaling returns errors.

Solutions:

  1. Confirm process type in Procfile
  2. Check dyno quota
  3. Ensure app is not suspended
  4. Review usage: heroku ps --app=my-app

Issue: Build Fails with Timeout

Symptoms: Build hangs or times out.

Solutions:

  1. Use correct buildpacks
  2. Cache dependencies
  3. Split large builds
  4. Use pre-built slugs

Issue: Rate Limit Exceeded

Symptoms: HTTP 429 responses.

Solutions:

  1. Implement request queuing
  2. Use exponential backoff
  3. Batch requests
  4. 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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%');
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. GitHub push triggers workflow
  2. CI runs tests
  3. Heroku API creates build from source blob
  4. Promote through staging → production
  5. 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)