An API proxy for bunny.net that enables scoped and limited API keys. Perfect for ACME DNS-01 validation and other use cases where you want to restrict access to specific DNS zones or operations.
- API-Only Architecture - Pure REST API with no web UI; ideal for automation
- Scoped Tokens - Create tokens with granular permissions for specific zones and operations
- Admin Tokens - Full-access tokens for management operations
- Bootstrap Security - Master key can only create the first admin token, then is locked out
- DNS API Support - Proxies bunny.net DNS operations (7 endpoints: zones and records management)
- SQLite Storage - Lightweight, embedded database with persistent storage
- Structured Logging - JSON-formatted logs with runtime log level control
- Health Endpoints - Liveness and readiness probes for container orchestration
- Comprehensive Tests - >85% test coverage with security scanning
docker run -d \
-p 8080:8080 \
-v bunny-proxy-data:/data \
ghcr.io/sipico/bunny-api-proxy:latestAll configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
info |
Log level (debug, info, warn, error) |
LISTEN_ADDR |
:8080 |
Server listen address |
DATABASE_PATH |
/data/proxy.db |
SQLite database path |
BUNNY_API_URL |
https://api.bunny.net |
bunny.net API endpoint |
On first run, use your bunny.net master API key to create an admin token:
# Create the first admin token using master key
curl -X POST http://localhost:8080/admin/api/tokens \
-H "AccessKey: YOUR_BUNNY_NET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "primary-admin", "is_admin": true}'Response:
{
"id": 1,
"name": "primary-admin",
"token": "abc123...",
"is_admin": true
}Important: Save the returned token value - it is shown only once. After the first admin token is created, the master key is locked out and all further management must use admin tokens.
All API requests require the AccessKey header:
curl -H "AccessKey: YOUR_TOKEN" http://localhost:8080/admin/api/whoamiHealth endpoints are available at both root and /admin paths for compatibility.
Liveness check - returns OK if the process is running.
curl http://localhost:8080/health
# or
curl http://localhost:8080/admin/health{"status":"ok"}Readiness check - returns OK if the database is accessible.
curl http://localhost:8080/ready
# or
curl http://localhost:8080/admin/ready{"status":"ok","database":"connected"}All admin endpoints are under /admin/api and require authentication via the AccessKey header.
Returns information about the current token.
curl -H "AccessKey: YOUR_TOKEN" http://localhost:8080/admin/api/whoamiResponse for admin token:
{
"token_id": 1,
"name": "primary-admin",
"is_admin": true,
"is_master_key": false
}Response for scoped token:
{
"token_id": 2,
"name": "acme-client",
"is_admin": false,
"is_master_key": false,
"permissions": [
{
"id": 1,
"zone_id": 12345,
"allowed_actions": ["list_records", "add_record", "delete_record"],
"record_types": ["TXT"]
}
]
}List all tokens (admin only).
curl -H "AccessKey: YOUR_ADMIN_TOKEN" http://localhost:8080/admin/api/tokens[
{"id": 1, "name": "primary-admin", "is_admin": true, "created_at": "2024-01-15T10:30:00Z"},
{"id": 2, "name": "acme-client", "is_admin": false, "created_at": "2024-01-15T11:00:00Z"}
]Create a new token (admin only, or master key during bootstrap).
Create an admin token:
curl -X POST http://localhost:8080/admin/api/tokens \
-H "AccessKey: YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "backup-admin", "is_admin": true}'Create a scoped token:
curl -X POST http://localhost:8080/admin/api/tokens \
-H "AccessKey: YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-client",
"is_admin": false,
"zones": [12345],
"actions": ["list_records", "add_record", "delete_record"],
"record_types": ["TXT"]
}'Response:
{
"id": 2,
"name": "acme-client",
"token": "def456...",
"is_admin": false
}Note: The token is shown only once. Store it securely.
Get token details (admin only).
curl -H "AccessKey: YOUR_ADMIN_TOKEN" http://localhost:8080/admin/api/tokens/2{
"id": 2,
"name": "acme-client",
"is_admin": false,
"created_at": "2024-01-15T11:00:00Z",
"permissions": [
{
"id": 1,
"zone_id": 12345,
"allowed_actions": ["list_records", "add_record", "delete_record"],
"record_types": ["TXT"]
}
]
}Delete a token (admin only).
curl -X DELETE -H "AccessKey: YOUR_ADMIN_TOKEN" http://localhost:8080/admin/api/tokens/2Returns 204 No Content on success. You cannot delete the last admin token.
Add a permission to a scoped token (admin only).
curl -X POST http://localhost:8080/admin/api/tokens/2/permissions \
-H "AccessKey: YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"zone_id": 67890,
"allowed_actions": ["list_records"],
"record_types": ["A", "AAAA"]
}'{
"id": 2,
"zone_id": 67890,
"allowed_actions": ["list_records"],
"record_types": ["A", "AAAA"]
}Remove a permission from a token (admin only).
curl -X DELETE -H "AccessKey: YOUR_ADMIN_TOKEN" \
http://localhost:8080/admin/api/tokens/2/permissions/1Returns 204 No Content on success.
Change the runtime log level (admin only).
curl -X POST http://localhost:8080/admin/api/loglevel \
-H "AccessKey: YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"level": "debug"}'{"level": "debug"}These endpoints proxy requests to bunny.net. Use a scoped token with appropriate permissions.
List all DNS zones.
curl -H "AccessKey: YOUR_TOKEN" http://localhost:8080/dnszoneCreate a new DNS zone.
curl -X POST http://localhost:8080/dnszone \
-H "AccessKey: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"Domain": "example.com"}'Get zone details.
curl -H "AccessKey: YOUR_TOKEN" http://localhost:8080/dnszone/12345Delete a DNS zone.
curl -X DELETE -H "AccessKey: YOUR_TOKEN" \
http://localhost:8080/dnszone/12345List records in a zone.
curl -H "AccessKey: YOUR_TOKEN" http://localhost:8080/dnszone/12345/recordsAdd a record to a zone.
curl -X POST http://localhost:8080/dnszone/12345/records \
-H "AccessKey: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"Type": 3, "Name": "_acme-challenge", "Value": "validation-token", "Ttl": 300}'Delete a record from a zone.
curl -X DELETE -H "AccessKey: YOUR_TOKEN" \
http://localhost:8080/dnszone/12345/records/67890All errors return JSON with a consistent structure:
{
"error": "error_code",
"message": "Human-readable description",
"hint": "Optional suggestion for resolution"
}Common error codes:
invalid_request- Malformed request body or invalid parametersinvalid_credentials- Missing or invalid API keyadmin_required- Endpoint requires an admin tokenmaster_key_locked- Master key cannot be used after bootstrapnot_found- Resource not foundcannot_delete_last_admin- Cannot delete the only admin tokenno_admin_token_exists- Must create admin token first during bootstrapinternal_error- Server error
git clone https://github.com/sipico/bunny-api-proxy.git
cd bunny-api-proxy
go build -o bunny-api-proxy ./cmd/bunny-api-proxy
./bunny-api-proxy# Run tests with coverage
go test -race -cover ./...
# Run linter
golangci-lint run
# Security scan
govulncheck ./...See ARCHITECTURE.md for technical details.
AGPL v3 - see LICENSE for details.
Commercial licenses available for organizations that want to use this software without AGPL v3 copyleft requirements.
Bug reports and feature requests welcome as GitHub Issues. See CONTRIBUTING.md for details.