Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

API Monitoring with Prometheus and Grafana

By Kokil Thapa | Last reviewed: September 2026

Your API can return 200 OK while latency quietly doubles and error rates creep up. API monitoring with Prometheus and Grafana fixes that gap by collecting time-series metrics from your application and infrastructure, storing them in Prometheus, and visualising trends in Grafana before users open support tickets. On production Laravel and payment-gateway integrations I've maintained, scrape-based monitoring caught webhook timeouts and queue backlogs that log tailing alone missed. This guide walks through instrumentation, scrape config, RED dashboards, and alerts you can deploy on Ubuntu with PHP 8.3+ or Laravel 12/13.

For teams shipping REST API development in Nepal or abroad, Prometheus fits pull-model observability well. You control retention, queries, and alert rules without locking into a single SaaS vendor. Grafana sits on top as the visual layer. Together they answer the questions operators actually ask: which endpoint is slow, which tenant hits rate limits, and did the last deploy change p95 latency.

What Is API Monitoring with Prometheus and Grafana?

Prometheus is a time-series database built around periodic HTTP scrapes. Your API (or a sidecar exporter) exposes metrics in OpenMetrics text format. Prometheus pulls those samples every 15–60 seconds and stores them with labels such as method, route, and status. Grafana queries Prometheus with PromQL and renders panels, heatmaps, and tables.

Grafana does not collect data itself in this stack. It reads from Prometheus (and optionally Loki or Tempo if you add logs and traces later). That separation keeps concerns clean: Prometheus owns storage and alerting rules; Grafana owns presentation.

API Monitoring StackClientsWeb / MobileREST APILaravel / PHP/metricsOpenMetricsPrometheusScrape + StoreGrafanaDashboardsPull model: Prometheus scrapes /metrics on intervalAlertmanager fires when PromQL rules breach thresholds
Pull-based API monitoring with Prometheus and Grafana: clients hit your API; Prometheus scrapes exposed metrics; Grafana visualises PromQL queries.

The stack differs from passive log aggregation. Metrics are numeric samples you can aggregate, rate, and alert on in milliseconds. That makes it ideal for SLO tracking and capacity planning on APIs that handle bookings, payments, or document uploads.

Core components you will deploy

  • Instrumented API — exposes counters, gauges, and histograms at /metrics.
  • Prometheus server — scrapes targets, evaluates recording rules, fires alerts.
  • Grafana — dashboards, variables, annotations for deploy markers.
  • Alertmanager — routes notifications to Slack, email, or PagerDuty. See the dedicated Alertmanager alerting guide for routing trees.
  • Exporters — optional sidecars for Nginx, MySQL 9.7, or Redis 8.10 when the app cannot expose infra metrics directly.

How Do You Instrument a REST API for Prometheus?

Instrumentation means emitting metrics at request boundaries. For HTTP APIs, the RED method is the standard starting point: Rate (requests per second), Errors (failed requests), and Duration (latency distribution). Google SRE calls these part of the four golden signals; for APIs, RED maps cleanly onto route-level labels.

In PHP and Laravel applications, the promphp/prometheus_client_php library registers a registry and renders OpenMetrics output. Wrap middleware around your API routes to observe each request.

Laravel middleware example

<?php
namespace App\Http\Middleware;

use Closure;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;

class PrometheusMetrics
{
    public function handle($request, Closure $next)
    {
        $registry = new CollectorRegistry(new Redis([
            'host' => env('REDIS_HOST', '127.0.0.1'),
        ]));

        $counter = $registry->getOrRegisterCounter(
            'api', 'http_requests_total',
            'Total HTTP requests',
            ['method', 'route', 'status']
        );

        $histogram = $registry->getOrRegisterHistogram(
            'api', 'http_request_duration_seconds',
            'Request latency',
            ['method', 'route'],
            [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
        );

        $start = microtime(true);
        $response = $next($request);
        $duration = microtime(true) - $start;

        $route = $request->route()?->getName() ?? 'unknown';
        $status = (string) $response->getStatusCode();

        $counter->inc([$request->method(), $route, $status]);
        $histogram->observe($duration, [$request->method(), $route]);

        return $response;
    }
}

Register a dedicated metrics route outside normal auth middleware. Protect it with network ACLs or basic auth so metric endpoints are not public. Pair this pattern with Laravel API best practices so labels stay bounded — never use raw user IDs as label values.

Metrics route controller

<?php
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Redis;

Route::get('/metrics', function () {
    $registry = new CollectorRegistry(new Redis(['host' => '127.0.0.1']));
    $renderer = new RenderTextFormat();
    return response($renderer->render($registry->getMetricFamilySamples()))
        ->header('Content-Type', RenderTextFormat::MIME_TYPE);
})->middleware('metrics.ip_allowlist');

On booking APIs I've worked on, I also track business counters: payment_callbacks_total, queue_jobs_failed_total, and webhook_retries_total. Those catch integration failures that still return HTTP 200 to the caller. Validate payloads with your JSON formatter tool during development, but production health belongs in Prometheus counters.

RED Method for APIsRatereq/s by routeErrors5xx + 4xx ratioDurationp50 p95 p99Prometheus Histogram Bucketshistogram_quantile(0.95, rate(...))Label every series with method + route name, not raw URLsHigh-cardinality labels explode memory use
RED metrics — rate, errors, duration — form the baseline panels for API monitoring with Prometheus and Grafana dashboards.

How Do You Configure Prometheus to Scrape API Metrics?

Prometheus reads targets from prometheus.yml. Each job defines scrape interval, path, and labels applied to every series from that target. Keep scrape intervals at 15s for APIs under active development and 30–60s for stable production unless you have a clear reason to go faster.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'laravel-api'
    metrics_path: /metrics
    static_configs:
      - targets: ['api.internal:8080']
        labels:
          env: production
          service: bookings-api

  - job_name: 'nginx-exporter'
    static_configs:
      - targets: ['nginx-exporter:9113']

  - job_name: 'redis-exporter'
    static_configs:
      - targets: ['redis-exporter:9121']

rule_files:
  - /etc/prometheus/rules/api-alerts.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

For multi-host setups, file-based service discovery or Docker labels scales better than hard-coded IPs. On Ubuntu 22/24 servers I manage, Prometheus runs as a systemd unit with config deployed through the same GitLab CI pipeline used for application releases. That mirrors the approach in our Ubuntu server monitoring guide.

Verify scrapes before building dashboards

  1. Open http://prometheus:9090/targets and confirm state is UP.
  2. Run up{job="laravel-api"} in the Prometheus graph UI.
  3. Query rate(api_http_requests_total[5m]) after generating traffic.
  4. Check for label cardinality with count by (route) (api_http_requests_total).

If targets show DOWN, check firewall rules between Prometheus and the API host. The scrape is an inbound HTTP GET from Prometheus to your app. UFW must allow that path on the metrics port. Official reference: Prometheus configuration documentation.

Which Grafana Dashboards Matter Most for API Health?

Start with one overview dashboard and one drill-down per critical service. The overview shows global RPS, error percentage, and p95 latency. Drill-downs filter by route, status code, or deployment version using Grafana template variables.

Import community dashboard ID 10826 as a starting skeleton, then replace generic node metrics with your api_http_* series. Our Grafana dashboards practical guide covers variables, units, and annotation layers for deploy markers.

Essential PromQL queries for API panels

# Request rate by route
sum by (route) (rate(api_http_requests_total[5m]))

# Error ratio (5xx)
sum(rate(api_http_requests_total{status=~"5.."}[5m]))
  / sum(rate(api_http_requests_total[5m]))

# p95 latency
histogram_quantile(0.95,
  sum by (le, route) (rate(api_http_request_duration_seconds_bucket[5m]))
)

# Apdex-style threshold (500ms)
sum(rate(api_http_request_duration_seconds_bucket{le="0.5"}[5m]))
  / sum(rate(api_http_request_duration_seconds_count[5m]))

Add rows for dependency health: MySQL slow queries, Redis memory, queue depth. On a legal-tech client portal with document uploads, correlating API latency with disk I/O caught a storage bottleneck that application logs masked. Cross-link infra panels with the full Prometheus and Grafana monitoring stack article for node-level exporters.

ApproachStrengthsWeaknessesBest for
Prometheus + Grafana (pull)PromQL, long retention, open format, no agent billingYou operate the stack; cardinality discipline requiredLaravel/PHP APIs on VPS or dedicated servers
StatsD / Graphite pushSimple UDP fire-and-forgetWeaker histogram support; aggregation gapsLegacy apps with existing StatsD libs
APM SaaS (Datadog, New Relic)Turnkey dashboards, distributed tracingCost scales with span volume; vendor lock-inTeams without ops headcount
Netdata (see comparison article)Zero-config host metricsLess custom API histogram controlQuick server baseline before full API RED
Logs-only (ELK, Loki)Rich request context, stack tracesExpensive queries for rate/latency SLOsComplement metrics, not replace them

For small teams in Nepal running APIs on a single EC2 or local VPS (Rs 3,000–8,000/month hosting, ~USD 22–60), self-hosted Prometheus on the same network segment is often cheaper than SaaS APM at moderate traffic. Budget Alertmanager notification channels before you budget Grafana Enterprise plugins.

Grafana API Dashboard LayoutRow 1: Global RPS | Error % | p95 Latency | UptimeTime SeriesRate by routeVariable: $routeHeatmapLatency bucketsLast 6 hoursError Breakdown4xx vs 5xx tableDependenciesRedis + MySQL panelsAdd deploy annotations to correlate latency spikes with releases
Typical Grafana dashboard rows for API monitoring with Prometheus and Grafana: global SLIs on top, route drill-down and dependency health below.

How Should You Alert on API Metrics Without Alert Fatigue?

Alerts belong in Prometheus rule files, not Grafana UI alone. Grafana can alert, but centralising in Prometheus keeps evaluation co-located with the data and feeds Alertmanager for deduplication and silencing.

groups:
  - name: api-alerts
    rules:
      - alert: ApiHighErrorRate
        expr: |
          sum(rate(api_http_requests_total{status=~"5.."}[5m]))
            / sum(rate(api_http_requests_total[5m])) > 0.05
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "API error rate above 5% for 10 minutes"

      - alert: ApiLatencyP95High
        expr: |
          histogram_quantile(0.95,
            sum(rate(api_http_request_duration_seconds_bucket[5m]))
          ) > 2
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "API p95 latency above 2 seconds"

      - alert: ApiMetricsTargetDown
        expr: up{job="laravel-api"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Prometheus cannot scrape API metrics"

Use the for clause to avoid paging on single blips. Pair threshold alerts with SLO burn-rate rules when traffic justifies the complexity. Route critical payment endpoints — like those described in Laravel rate limiting and throttling — to a separate Alertmanager receiver with shorter for windows.

Alert routing principles

  • Warning → Slack channel; no phone call.
  • Critical → on-call rotation with acknowledgement timeout.
  • Info → ticket queue for next business day.
  • Silence alerts during planned deploys using Alertmanager silences, not by disabling rules.

Grafana alerting docs are useful for panel thresholds, but production API SLOs should live in version-controlled YAML beside your Prometheus config. Reference: Grafana alerting documentation.

API Alert PipelinePrometheusRule evalAlertmanagerDedup + routeSlackEmailPagerDutyfor: 10mprevents flappingWire payment and auth endpoints to faster receivers
Prometheus evaluates API alert rules and sends firing alerts to Alertmanager, which routes notifications by severity label.

What Production Gotchas Break API Monitoring Stacks?

Most failures I see are operational, not PromQL syntax errors. Card label explosion is the silent killer. Every unique URL path as a label creates a new time series. Use named routes, not $request->path(), and cap cardinality below a few thousand active series per job.

Second: scraping through a load balancer without sticky awareness double-counts if each backend exposes independent counters. Either scrape one canary instance or export aggregated metrics from the gateway layer. If you terminate TLS at Kong or another API gateway, expose gateway-level RED metrics there and treat app metrics as a secondary signal.

Third: histogram bucket boundaries must match your SLO thresholds. Default Go buckets top out at 10 seconds. PHP APIs serving PDF generation or image transforms need buckets up to 30–60 seconds or your p99 flatlines at the bucket ceiling.

Fourth: secure the metrics endpoint. Unauthenticated /metrics leaks route names and traffic patterns. Restrict by IP, mTLS, or internal VPC only. This sits alongside proper API authentication with Passport or Sanctum on public routes — different surfaces, same security mindset.

Fifth: after deploys, reload PHP-FPM so opcache picks up middleware changes. I've debugged "metrics stuck at zero" that was simply stale PHP workers after a symlink swap. The same deploy pipeline notes apply in Linux system administration engagements.

Gateway and multi-service patterns

When APIs sit behind Traefik or Nginx, run nginx-prometheus-exporter or enable Traefik's built-in metrics endpoint as a separate scrape job. Correlate edge 502 rates with application 500 rates to distinguish proxy misconfig from code regressions. For design context, read REST API design best practices before you label routes inconsistently across services.

On Adventure Third Pole Trek, a Laravel + Livewire booking API benefited from queue-depth metrics alongside HTTP RED. Failed job counters predicted checkout failures minutes before HTTP error rates moved. That pattern applies to any async API processing payments or notifications.

If you need a lighter host baseline while building Prometheus coverage, Netdata zero-config monitoring fills gaps on CPU and disk. Migrate critical SLIs to Prometheus once histogram instrumentation lands.

Key Takeaways

  • Expose OpenMetrics at a protected /metrics endpoint and instrument every API route with RED counters and histograms.
  • Keep Prometheus labels low-cardinality: use route names, never user IDs or full URL paths.
  • Build Grafana overview plus drill-down dashboards with p95 latency and error-ratio panels tied to deploy annotations.
  • Define Prometheus alert rules with for durations and route severity-specific Alertmanager receivers.
  • Scrape through internal networks only, reload PHP-FPM after deploys, and tune histogram buckets to your real SLOs.
  • Complement HTTP metrics with business counters for webhooks, queues, and third-party API callbacks.

People Also Ask

Can Prometheus monitor APIs behind Kubernetes?

Yes. Use Kubernetes pod annotations prometheus.io/scrape: "true" and prometheus.io/port so Prometheus discovers pods automatically. ServiceMonitor CRDs with the Prometheus Operator are the managed alternative on clusters. The scrape model stays identical: HTTP pull against /metrics.

Do you need Grafana if Prometheus already has a UI?

Prometheus graphs single queries adequately for debugging. Grafana adds dashboard variables, team sharing, annotations, and mixed data sources. For any API watched by non-engineers — product owners, support leads — Grafana is worth the extra container.

How is API monitoring different from uptime checks?

Uptime probes hit one synthetic URL every few minutes. Prometheus scrapes real request metrics from production traffic continuously. You see per-route latency distributions and error mixes that a binary up/down check cannot surface.

What metrics should a Laravel API export first?

Start with http_requests_total, http_request_duration_seconds, and queue_jobs_failed_total. Add payment callback and external API latency histograms next if your app integrates gateways like eSewa, Khalti, or Stripe.

Ship API Monitoring You Can Act On

API monitoring with Prometheus and Grafana turns opaque production behaviour into graphs and alerts you can trust. Instrument with RED metrics, scrape on a steady interval, dashboard p95 and error ratio before launch day, and wire Alertmanager before the first incident — not after. If you want help instrumenting a Laravel API, hardening scrape paths, or folding monitoring into your deploy pipeline, contact us or explore testing and optimization services and ongoing support and maintenance. For a broader platform view, see building RESTful APIs with Laravel and custom software development options.

Frequently Asked Questions

Your API exposes /metrics; Prometheus scrapes time-series samples; Grafana charts rate, errors, and latency; Alertmanager sends threshold alerts.

Prometheus uses a pull model. Your instrumented API or a sidecar exporter serves counters, gauges, and histograms in OpenMetrics text format at an HTTP /metrics path. Prometheus reads targets from prometheus.yml and scrapes those samples every 15–60 seconds, storing them with labels such as method, route, and status. Grafana does not collect data in this stack; it queries Prometheus with PromQL and renders dashboards. That separation keeps storage, alerting rules, and presentation cleanly divided between Prometheus and Grafana.

RED tracks Rate (requests per second), Errors (failed requests), and Duration (latency distribution) — the standard starting point for HTTP API instrumentation.

Use the promphp/prometheus_client_php library with a Redis-backed CollectorRegistry. Register Laravel middleware on API routes to increment api_http_requests_total counters and observe api_http_request_duration_seconds histograms at request boundaries. Label by method, named route, and status — never raw user IDs. Expose a dedicated /metrics route outside normal auth, protected by network ACLs or basic auth. On booking APIs I have maintained, I also add business counters like payment_callbacks_total, queue_jobs_failed_total, and webhook_retries_total to catch integration failures that still return HTTP 200.

For small teams on a single EC2 or Nepal VPS, hosting runs Rs 3,000–8,000/month (~USD 22–60). Self-hosted Prometheus on the same network segment is often cheaper than SaaS APM at moderate traffic. Budget Alertmanager notification channels before Grafana Enterprise plugins.

Define scrape jobs in prometheus.yml with job_name, metrics_path, targets, and labels. Set scrape_interval to 15s during active development and 30–60s for stable production. A typical laravel-api job points at api.internal:8080/metrics with env and service labels. Add separate jobs for nginx-exporter and redis-exporter when you need infrastructure context. Reference rule_files for api-alerts.yml and point alerting at Alertmanager on port 9093. On Ubuntu 22/24 servers I manage, Prometheus runs as a systemd unit deployed through the same GitLab CI pipeline used for application releases.

Start with one overview dashboard showing global requests per second, error percentage, and p95 latency, plus one drill-down per critical service filtered by route, status code, or deployment version using template variables. Import community dashboard ID 10826 as a skeleton, then replace generic node metrics with your api_http_* series. Add rows for dependency health: MySQL slow queries, Redis memory, and queue depth. On a legal-tech client portal with document uploads, correlating API latency with disk I/O panels caught a storage bottleneck that application logs alone did not reveal.

Request rate by route: sum by (route) (rate(api_http_requests_total[5m])). Error ratio for 5xx responses: sum(rate(api_http_requests_total{status=~"5.."}[5m])) divided by sum(rate(api_http_requests_total[5m])). p95 latency: histogram_quantile(0.95, sum by (le, route) (rate(api_http_request_duration_seconds_bucket[5m]))). Apdex-style threshold at 500ms: sum(rate(api_http_request_duration_seconds_bucket{le="0.5"}[5m])) divided by sum(rate(api_http_request_duration_seconds_count[5m])). Verify scrapes first with up{job="laravel-api"} and check label cardinality with count by (route) (api_http_requests_total).

For Laravel and PHP APIs on VPS or dedicated servers, Prometheus plus Grafana gives you PromQL, long retention, open format, and no per-agent billing — but you operate the stack and must enforce cardinality discipline. APM SaaS tools like Datadog and New Relic offer turnkey dashboards and distributed tracing, yet cost scales with span volume and creates vendor lock-in. StatsD push suits legacy apps but has weaker histogram support. Logs-only stacks like ELK or Loki complement metrics but are expensive for rate and latency SLO queries. Choose self-hosted Prometheus when you have ops headcount and want full control.

Define alert rules in version-controlled YAML beside Prometheus config, not only in the Grafana UI. Use a for clause to avoid paging on single blips — for example, ApiHighErrorRate fires only after error ratio exceeds 5% for 10 minutes, and ApiLatencyP95High after p95 exceeds 2 seconds for 15 minutes. Route warnings to Slack, critical alerts to on-call with acknowledgement timeouts, and info-level items to a ticket queue. Silence alerts during planned deploys through Alertmanager silences rather than disabling rules. Route critical payment endpoints to a separate Alertmanager receiver with shorter for windows.

When up{job="laravel-api"} equals zero, Prometheus cannot reach your /metrics endpoint. The scrape is an inbound HTTP GET from Prometheus to your app, so UFW or firewall rules must allow that path on the metrics port. Confirm the target appears UP at http://prometheus:9090/targets. Check that the API host and port in static_configs match the running service. If metrics recently went to zero after a deploy, reload PHP-FPM so opcache picks up middleware changes — I have debugged stuck-at-zero metrics that were simply stale PHP workers after a symlink swap.

Label explosion is the silent killer. Every unique URL path as a label creates a new time series. Use named Laravel routes via $request->route()->getName(), never raw $request->path(), and keep active series below a few thousand per job. Unbounded labels like user IDs or tenant slugs multiply storage cost and slow PromQL queries. Before building dashboards, run count by (route) (api_http_requests_total) to audit cardinality. Bounded labels for method, route name, and status give you drill-down power without overwhelming Prometheus retention.

Register the metrics route outside normal authentication middleware and protect it with network ACLs, IP allowlists, basic auth, mTLS, or internal VPC-only access. An unauthenticated /metrics endpoint leaks route names and traffic patterns useful to attackers. This is a separate surface from public API authentication with Passport or Sanctum — same security mindset, different exposure. On production Laravel applications I maintain, metrics.ip_allowlist middleware restricts scrapes to the Prometheus host. Never expose /metrics on the public internet without restriction.

Default histogram buckets often top out at 10 seconds. PHP APIs serving PDF generation, image transforms, or large document uploads need buckets up to 30–60 seconds or your p99 latency flatlines at the bucket ceiling and misleads SLO dashboards. Match bucket boundaries to your actual SLO thresholds — if Apdex targets 500ms, include a le="0.5" bucket. The article example uses buckets at 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, and 10 seconds as a starting point, extended when slow endpoints are expected.

HTTP RED catches request-level health but misses integration failures that return 200 OK to callers. On booking and payment APIs I have worked on, add counters for payment_callbacks_total, queue_jobs_failed_total, and webhook_retries_total. On Adventure Third Pole Trek, queue-depth metrics alongside HTTP RED predicted checkout failures minutes before HTTP error rates moved. Pair application counters with dependency panels: MySQL slow queries via exporters, Redis 8.10 memory, and Nginx edge 502 rates. Correlating gateway errors with application 500 rates distinguishes proxy misconfig from code regressions.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: