carbon-platform-api is an independent public portfolio project demonstrating backend and platform engineering with Python, FastAPI, PostgreSQL, Redis, Docker Compose, Prometheus, Grafana, structured logging, health/readiness checks, tests, and CI.
The API tracks public-safe sample compute usage, calculates deterministic demo carbon estimates, persists usage samples, and exposes basic summary reports. It is intentionally a portfolio/demo service, not an authoritative carbon accounting product.
This repository uses only public-safe sample code, fake data, and local placeholder configuration. Do not add employer code, private data, non-public URLs or hostnames, credentials, screenshots, diagrams, or architecture. The project must not imply endorsement by any employer or organization.
Current implemented capabilities:
- Python 3.12 FastAPI application using a
src/layout. - Environment-backed settings loaded from
CARBON_API_*variables. - Structured JSON request logging with
X-Request-IDpropagation. - Lightweight liveness, dependency readiness, and Prometheus metrics endpoints.
- Async SQLAlchemy persistence, Alembic migrations, and repository boundaries.
- Workspace create/list/fetch endpoints.
- Usage sample ingestion with persisted demo emissions estimates.
- Summary reporting across all workspaces or one workspace.
- Optional API key authentication for business endpoints.
- Mockable carbon intensity HTTP client and Redis cache abstractions.
- Docker Compose local stack for API, PostgreSQL, Redis, Prometheus, and Grafana.
- Public-safe GitHub Actions CI for linting, formatting, type checks, tests, migrations, and Compose validation.
Out of scope today: OAuth, user accounts, password storage, deployment automation/IaC, hosted monitoring integrations, tracing, direct HTTP carbon-intensity lookup, and production-grade carbon factors.
- Python 3.12
uvfor dependency management and command executionmake- Docker with Docker Compose v2 for the local container stack
Copy the safe local defaults if you want a .env file:
cp example.env .envValidate, build, and start the full local stack:
docker compose config
docker compose build
docker compose up --detachApply database migrations before using workspace, usage ingestion, or reporting endpoints:
CARBON_API_DATABASE_URL=postgresql+asyncpg://carbon_platform_api:local_dev_password@localhost:5432/carbon_platform_api uv run alembic upgrade headCheck the API and local observability services:
curl -i http://localhost:8000/healthz
curl -i http://localhost:8000/readyz
curl -i http://localhost:8000/metrics
curl -i http://localhost:9090/-/healthy
curl -i http://localhost:3000/api/healthStop and remove local containers, networks, and volumes:
docker compose down --volumes --remove-orphansFor a longer fake-data flow, see Sample API walkthrough.
Install dependencies:
make installStart the API process:
make runGET /healthz works without PostgreSQL or Redis. GET /readyz and business endpoints require reachable PostgreSQL/Redis settings, and business endpoints require Alembic migrations to be applied.
Application settings are loaded from environment variables with the CARBON_API_ prefix.
| Variable | Default | Purpose |
|---|---|---|
CARBON_API_APP_NAME |
carbon-platform-api |
FastAPI application name. |
CARBON_API_APP_VERSION |
0.1.0 |
FastAPI application version. |
CARBON_API_ENVIRONMENT |
local |
Runtime environment label. |
CARBON_API_LOG_LEVEL |
INFO |
Standard library log level for structured JSON logs. |
CARBON_API_DOCS_ENABLED |
false |
Enables /docs, /redoc, and /openapi.json only when set to true. |
CARBON_API_AUTH_ENABLED |
false |
Enables API key checks on business endpoints when set to true. |
CARBON_API_AUTH_API_KEYS |
local-demo-api-key |
Comma-separated local placeholder API keys accepted through X-API-Key; replace for any real environment. |
CARBON_API_DATABASE_URL |
postgresql+asyncpg://carbon_platform_api:local_dev_password@localhost:5432/carbon_platform_api |
Async SQLAlchemy PostgreSQL URL. |
CARBON_API_REDIS_URL |
redis://localhost:6379/0 |
Redis URL used by cache implementations and readiness checks. |
CARBON_API_CARBON_INTENSITY_PROVIDER_BASE_URL |
https://carbon-intensity.example.invalid |
Public-safe placeholder base URL for the carbon intensity provider client. |
CARBON_API_CARBON_INTENSITY_PROVIDER_TIMEOUT_SECONDS |
2.0 |
Timeout, in seconds, for carbon intensity provider HTTP calls. |
CARBON_API_CARBON_INTENSITY_CACHE_TTL_SECONDS |
900 |
Redis cache TTL, in seconds, for successful carbon intensity provider responses. |
FastAPI docs and OpenAPI routes are disabled by default. Enable them only for local exploration:
CARBON_API_DOCS_ENABLED=true make run| Method and path | Purpose |
|---|---|
GET /healthz |
Lightweight liveness check. Returns {"status":"ok"} and an X-Request-ID header. |
GET /readyz |
Checks PostgreSQL and Redis dependency connectivity. |
GET /metrics |
Returns Prometheus text exposition metrics. |
POST /workspaces |
Creates a workspace with a unique public-safe name; requires X-API-Key when auth is enabled. |
GET /workspaces |
Lists workspaces; requires X-API-Key when auth is enabled. |
GET /workspaces/{workspace_id} |
Fetches one workspace by UUID; requires X-API-Key when auth is enabled. |
POST /workspaces/{workspace_id}/usage-samples |
Ingests one usage sample and stores calculated demo emissions fields; requires X-API-Key when auth is enabled. |
GET /workspaces/{workspace_id}/reports/summary |
Returns summary totals for one workspace; requires X-API-Key when auth is enabled. |
GET /reports/summary |
Returns summary totals across all workspaces; requires X-API-Key when auth is enabled. |
If a request supplies X-Request-ID, the API propagates it to the response and completion log. Otherwise, the API generates one.
Authentication is disabled by default for local exploration. Enable simple API key checks for business endpoints with safe local placeholder values:
CARBON_API_AUTH_ENABLED=true CARBON_API_AUTH_API_KEYS=local-demo-api-key make run
curl -i -H 'X-API-Key: local-demo-api-key' http://127.0.0.1:8000/workspacesWhen auth is enabled, POST /workspaces, GET /workspaces, GET /workspaces/{workspace_id}, POST /workspaces/{workspace_id}/usage-samples, GET /workspaces/{workspace_id}/reports/summary, and GET /reports/summary require an X-API-Key header matching one configured key. Missing or invalid keys return 401 Unauthorized with a generic error. GET /healthz, GET /readyz, and GET /metrics intentionally remain unprotected so local health checks and Prometheus scraping continue to work.
The default key is a public-safe local placeholder, not a production secret. Do not log API keys, commit real secrets, or reuse the placeholder outside local demos.
Start PostgreSQL, then apply migrations from the host:
docker compose up --detach postgres
CARBON_API_DATABASE_URL=postgresql+asyncpg://carbon_platform_api:local_dev_password@localhost:5432/carbon_platform_api uv run alembic upgrade headRoll back all local migrations:
CARBON_API_DATABASE_URL=postgresql+asyncpg://carbon_platform_api:local_dev_password@localhost:5432/carbon_platform_api uv run alembic downgrade baseThe API does not auto-run migrations at startup.
Create a workspace:
curl -i \
-X POST http://127.0.0.1:8000/workspaces \
-H 'Content-Type: application/json' \
-d '{"name":"Demo Workspace"}'List workspaces:
curl -i http://127.0.0.1:8000/workspacesFetch one workspace, replacing the UUID with a value returned by create/list:
curl -i http://127.0.0.1:8000/workspaces/00000000-0000-0000-0000-000000000000Duplicate workspace names return 409 Conflict. Missing workspace IDs return 404 Not Found.
Ingest one fake compute usage sample for an existing workspace:
curl -i \
-X POST http://127.0.0.1:8000/workspaces/00000000-0000-0000-0000-000000000000/usage-samples \
-H 'Content-Type: application/json' \
-d '{
"provider":"sample-cloud",
"region":"sample-region-1",
"resource_type":"vcpu",
"usage_amount":"10",
"usage_unit":"vcpu_hour",
"measured_at":"2026-01-01T12:00:00Z",
"carbon_intensity_grams_co2e_per_kwh":"400"
}'Supported resource_type values are vcpu, memory, storage, and network. Supported usage_unit values are vcpu_hour, vcpu_minute, gb_hour, gb_minute, gb_month, tb_month, gb, mb, and tb; not every unit is compatible with every resource type.
The endpoint returns the persisted sample with calculated normalized_usage_amount, normalized_usage_unit, energy_kwh, estimated_grams_co2e, and factor_source fields. Missing workspaces return 404 Not Found; incompatible resource/unit pairs return 422 Unprocessable Content.
Fetch a summary for one workspace:
curl -i \
'http://127.0.0.1:8000/workspaces/00000000-0000-0000-0000-000000000000/reports/summary?start_time=2026-01-01T00:00:00Z&end_time=2026-02-01T00:00:00Z'Fetch a summary across all workspaces:
curl -i \
'http://127.0.0.1:8000/reports/summary?start_time=2026-01-01T00:00:00Z&end_time=2026-02-01T00:00:00Z'Report responses include the applied time_range, an overall total, and totals grouped in by_workspace, by_provider, and by_region. start_time is inclusive and end_time is exclusive. Supplied timestamps must be timezone-aware. Invalid ranges return 422 Unprocessable Content; missing workspace-scoped reports return 404 Not Found; empty reports return zero totals with empty groups.
Readiness checks PostgreSQL and Redis connectivity without requiring database migrations:
curl -i http://127.0.0.1:8000/readyzHealthy response body:
{"status":"ready","dependencies":[{"name":"database","status":"ok"},{"name":"redis","status":"ok"}]}Metrics are exposed in Prometheus text format:
curl -i http://127.0.0.1:8000/metricsThe output includes Python process metrics plus carbon_api_http_requests_total and carbon_api_http_request_duration_seconds labeled by method, route path, and status code.
Docker Compose includes local Prometheus at http://localhost:9090 and Grafana at http://localhost:3000. Prometheus scrapes api:8000/metrics inside the Compose network. Grafana provisions a Prometheus datasource and the Carbon Platform API Local Overview dashboard. Log in with the safe local placeholder values local_admin / local_dev_password unless overridden in .env.
Repository tests require PostgreSQL. Start it first or use the full quality gate, which starts an isolated PostgreSQL service automatically.
docker compose up --detach postgres
make test
make lint
make typecheckRun the full project gate:
scripts/quality-gate.shThe quality gate runs shell syntax checks, public-safety scanning, route-layering checks, Ruff, Ruff format check, mypy, Docker Compose config validation, Alembic migrations, and pytest with coverage. Carbon intensity client tests use fakes and httpx.MockTransport; they do not call a live third-party API.
GitHub Actions runs the CI workflow on pull requests and pushes to the default main branch. It uses public-safe local PostgreSQL service credentials for integration tests, caches uv dependencies from uv.lock, and runs the same substantive checks as the local quality gate. The workflow does not require repository secrets, upload coverage, run deployment jobs, or call external carbon providers.
A cloud-neutral deployment guide is available at Deployment guide. It documents a public-safe container deployment path, required runtime settings, health checks, explicit Alembic migrations, rollback planning, and operational risks. The guide intentionally does not add CI deployment jobs, real cloud accounts, real hostnames, credentials, or IaC files.
scripts/build-loop.sh runs bounded pi build cycles. It requires a clean working tree, pulls with git pull --ff-only when the branch has an upstream, refuses to start while ahead of upstream unless --allow-ahead or PI_BUILD_ALLOW_AHEAD=1 is set, and stops if the upstream changes during a cycle.
- The API does not auto-run Alembic migrations at startup.
- Carbon calculation factors and conversions are public-safe demo values only, not authoritative measurements.
- Usage ingestion requires caller-supplied carbon intensity values; it does not call the carbon intensity provider or Redis cache.
- Direct carbon intensity lookup is implemented behind service/client/cache abstractions but is not exposed through HTTP.
- Redis cache code exists, but current business endpoints do not use it yet.
- Reporting uses simple aggregate queries only; there are no time buckets, rollups, pagination, or materialized summaries.
- API key authentication is a simple portfolio-demo mechanism only; it does not include OAuth, user accounts, password storage, key rotation, rate limiting, or authorization roles.
- FastAPI docs/OpenAPI routes are disabled by default.
- Prometheus and Grafana are local Docker Compose services for metrics exploration only.
- A cloud-neutral deployment guide is documented, but no deployment automation, IaC, hosted monitoring integration, tracing integration, or secret-management integration is configured.
- Architecture
- Runbook
- Deployment guide
- Sample API walkthrough
- ADR 0001: Project scope
- ADR 0002: Layered architecture and mockable boundaries
- ADR 0003: Async PostgreSQL persistence and local Docker stack
- ADR 0004: Demo carbon calculation and cache-first intensity lookup
- ADR 0005: Observability and quality guardrails