HTTP/JSON API exposed by cmd/server. All endpoints accept and return application/json and use UTF-8.
http://<host>:<port> (default port 8080, configurable via HTTP_ADDR)application/json is required on every request that has a body400id fields are server-generated UUIDs when omitted from a create request.originated_id is an optional external identifier (e.g. ACCESS Record ID) — when supplied, it must be unique within its entity type.All timestamps are RFC 3339 / ISO 8601 with timezone, e.g. 2026-05-16T12:34:56.789Z. The server emits UTC.
Errors are returned with an appropriate HTTP status code and a JSON body:
{ "error": "human-readable message" }
| Status | Meaning | Triggered by |
|---|---|---|
400 Bad Request | Malformed JSON, unknown field, missing required field, or unknown foreign-key reference | request body validation, service.ErrInvalidInput |
404 Not Found | Requested record does not exist | service.ErrNotFound |
409 Conflict | Duplicate email or duplicate originated_id | service.ErrAlreadyExists |
500 Internal Server Error | Unexpected server / database failure (driver message is logged, never returned) | any other error |
GET /healthzLiveness probe. Always returns 200 when the process is accepting connections.
Response 200
{ "status": "ok" }
POST /organizationsCreate a new organization.
Required fields: name Optional fields: id (auto-generated if omitted), originated_id
Request
{ "name": "University of Example", "originated_id": "ACCESS-ORG-001" }
Response 201
{ "id": "8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "originated_id": "ACCESS-ORG-001", "name": "University of Example" }
Errors
400 — name is required.409 — an organization with the supplied originated_id already exists.curl -s -X POST http://localhost:8080/organizations \ -H 'Content-Type: application/json' \ -d '{"name":"University of Example","originated_id":"ACCESS-ORG-001"}'
GET /organizations/{id}Retrieve an organization by its ID.
Response 200
{ "id": "8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "originated_id": "ACCESS-ORG-001", "name": "University of Example" }
Errors
404 — no organization matches the supplied ID.POST /usersCreate a new user.
Required fields: organization_id, email Optional fields: id, first_name, last_name, middle_name
The referenced organization_id must already exist; emails must be unique.
Request
{ "organization_id": "8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.edu" }
Response 201
{ "id": "f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02", "organization_id": "8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.edu" }
Errors
400 — email, organization_id missing, or organization_id does not exist.409 — a user with this email already exists.curl -s -X POST http://localhost:8080/users \ -H 'Content-Type: application/json' \ -d '{ "organization_id":"8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "first_name":"Ada", "last_name":"Lovelace", "email":"ada@example.edu" }'
GET /users/{id}Retrieve a user by its ID.
Response 200
{ "id": "f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02", "organization_id": "8c4a1b2e-7d4f-4b6a-9a0c-2f3b9d1c8e21", "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.edu" }
Errors
404 — no user matches the supplied ID.POST /projectsCreate a new project.
Required fields: title, project_pi_id Optional fields: id, origination, originated_id, created_time (defaults to current UTC time)
The referenced project_pi_id must be an existing user. originated_id, when supplied, must be unique across projects.
Request
{ "title": "Climate Simulation 2026", "origination": "ACCESS", "originated_id": "ACCESS-PRJ-9000", "project_pi_id": "f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02" }
Response 201
{ "id": "3a8c2e7b-9d1f-4f5a-bc02-7a4d9e6c1bb1", "originated_id": "ACCESS-PRJ-9000", "title": "Climate Simulation 2026", "origination": "ACCESS", "project_pi_id": "f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02", "created_time": "2026-05-16T17:21:04.512Z" }
Errors
400 — title, project_pi_id missing, or the PI user does not exist.409 — a project with this originated_id already exists.curl -s -X POST http://localhost:8080/projects \ -H 'Content-Type: application/json' \ -d '{ "title":"Climate Simulation 2026", "origination":"ACCESS", "originated_id":"ACCESS-PRJ-9000", "project_pi_id":"f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02" }'
GET /projects/{id}Retrieve a project by its ID.
Response 200
{ "id": "3a8c2e7b-9d1f-4f5a-bc02-7a4d9e6c1bb1", "originated_id": "ACCESS-PRJ-9000", "title": "Climate Simulation 2026", "origination": "ACCESS", "project_pi_id": "f0c5a4d1-2b9e-4a7c-8d31-1c5b6e3d9f02", "created_time": "2026-05-16T17:21:04.512Z" }
Errors
404 — no project matches the supplied ID.A compute cluster represents a physical or logical HPC resource (e.g. a Slurm cluster) where allocations can be provisioned.
POST /compute-clustersCreate a new compute cluster.
Required fields: name Optional fields: id (auto-generated if omitted)
name must be unique across compute clusters.
Request
{ "name": "Delta" }
Response 201
{ "id": "9b0a7f1c-2c5d-4e1b-9a0f-22e8a5c2dcb1", "name": "Delta" }
Errors
400 — name is required.409 — a compute cluster with this name already exists.GET /compute-clustersList all compute clusters.
Response 200
[ { "id": "9b0a7f1c-2c5d-4e1b-9a0f-22e8a5c2dcb1", "name": "Delta" }, { "id": "1d4e6a3b-7c8f-49b2-bd34-7c1f9a4e5d10", "name": "Phoenix" } ]
GET /compute-clusters/{id}Retrieve a single compute cluster by its ID.
Errors
404 — no compute cluster matches the supplied ID.A compute allocation grants a project a budget of Service Units (SUs) on a specific compute cluster for a bounded time window.
POST /compute-allocationsCreate a new compute allocation.
Required fields: project_id, name, compute_cluster_id Optional fields: id, status (defaults to ACTIVE), initial_su_amount, start_time, end_time
Both project_id and compute_cluster_id must reference existing records. status is one of ACTIVE, INACTIVE, DELETED.
Request
{ "project_id": "3a8c2e7b-9d1f-4f5a-bc02-7a4d9e6c1bb1", "name": "Q2 2026 Climate Run", "compute_cluster_id": "9b0a7f1c-2c5d-4e1b-9a0f-22e8a5c2dcb1", "initial_su_amount": 100000, "start_time": "2026-04-01T00:00:00Z", "end_time": "2026-06-30T23:59:59Z" }
Response 201
{ "id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "project_id": "3a8c2e7b-9d1f-4f5a-bc02-7a4d9e6c1bb1", "name": "Q2 2026 Climate Run", "status": "ACTIVE", "compute_cluster_id": "9b0a7f1c-2c5d-4e1b-9a0f-22e8a5c2dcb1", "initial_su_amount": 100000, "start_time": "2026-04-01T00:00:00Z", "end_time": "2026-06-30T23:59:59Z" }
Errors
400 — required field missing, or project_id / compute_cluster_id does not exist.GET /compute-allocations/{id}Retrieve a compute allocation by its ID.
Errors
404 — no compute allocation matches the supplied ID.A compute allocation resource describes a hardware capability (e.g. GPU B200, CPU) that can be attached to one or more allocations.
POST /compute-allocation-resourcesCreate a new compute allocation resource.
Required fields: name, resource_type Optional fields: id, resource_amount
Request
{ "name": "GPU B200", "resource_type": "GPU", "resource_amount": 8 }
Response 201
{ "id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "name": "GPU B200", "resource_type": "GPU", "resource_amount": 8 }
Errors
400 — name or resource_type is missing.GET /compute-allocation-resourcesList all compute allocation resources.
Response 200
[ { "id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "name": "GPU B200", "resource_type": "GPU", "resource_amount": 8 } ]
GET /compute-allocation-resources/{id}Retrieve a compute allocation resource by its ID.
Errors
404 — no resource matches the supplied ID.A many-to-many join: an allocation can have many resources attached, and a resource can be attached to many allocations. Mappings are unique per (allocation, resource) pair, and are cascade-deleted when either parent is removed.
POST /compute-allocations/{id}/resourcesAttach an existing resource to a compute allocation.
Path parameters: {id} — the compute allocation ID. Required body fields: compute_allocation_resource_id
Request
{ "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff" }
Response 201
{ "id": "7e1d2c3b-4a5f-4b6c-9d8e-0011223344ff", "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff" }
Errors
400 — compute_allocation_resource_id missing, or either the allocation or the resource does not exist.409 — this resource is already attached to the allocation.DELETE /compute-allocations/{id}/resources/{resourceId}Detach a resource from a compute allocation.
Response 204 — empty body on success.
Errors
404 — no such mapping exists.GET /compute-allocations/{id}/resourcesList every compute allocation resource currently attached to the given compute allocation.
Response 200
[ { "id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "name": "GPU B200", "resource_type": "GPU", "resource_amount": 8 } ]
GET /compute-allocation-resources/{id}/allocationsList every compute allocation that has the given resource attached.
Response 200
[ { "id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "project_id": "3a8c2e7b-9d1f-4f5a-bc02-7a4d9e6c1bb1", "name": "Q2 2026 Climate Run", "status": "ACTIVE", "compute_cluster_id": "9b0a7f1c-2c5d-4e1b-9a0f-22e8a5c2dcb1", "initial_su_amount": 100000, "start_time": "2026-04-01T00:00:00Z", "end_time": "2026-06-30T23:59:59Z" } ]
A rate captures how many Service Units (SUs) are charged per unit of a compute allocation resource over a bounded time window. Multiple rates can exist for the same resource; usage at any instant is charged using the rate whose [start_time, end_time) window contains that instant.
Rates are cascade-deleted when their parent resource is deleted.
POST /compute-allocation-resource-ratesCreate a new rate for a compute allocation resource.
Required fields: compute_allocation_resource_id, rate, start_time, end_time Optional fields: id
Validation:
compute_allocation_resource_id must reference an existing resource.rate must be ≥ 0.start_time must be strictly before end_time.Request
{ "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "rate": 2.0, "start_time": "2026-01-01T00:00:00Z", "end_time": "2026-12-31T23:59:59Z" }
Response 201
{ "id": "55aa66bb-77cc-88dd-99ee-001122334455", "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "rate": 2.0, "start_time": "2026-01-01T00:00:00Z", "end_time": "2026-12-31T23:59:59Z" }
Errors
400 — required field missing, invalid time window, negative rate, or unknown compute_allocation_resource_id.GET /compute-allocation-resource-rates/{id}Retrieve a rate by its ID.
Errors
404 — no rate matches the supplied ID.GET /compute-allocation-resources/{id}/ratesList every rate ever defined for the given compute allocation resource, ordered by start_time ascending.
Response 200
[ { "id": "55aa66bb-77cc-88dd-99ee-001122334455", "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "rate": 2.0, "start_time": "2026-01-01T00:00:00Z", "end_time": "2026-12-31T23:59:59Z" } ]
GET /compute-allocation-resources/{id}/rates/effectiveReturn the rate currently in effect for the given resource. By default the server uses the current time; supply ?at=<RFC 3339 timestamp> to query an arbitrary instant.
A rate is “effective” at instant t when start_time <= t < end_time. If multiple rates overlap t, the one with the most recent start_time wins.
Examples
GET /compute-allocation-resources/c0a1.../rates/effective GET /compute-allocation-resources/c0a1.../rates/effective?at=2026-05-16T12:00:00Z
Response 200
{ "id": "55aa66bb-77cc-88dd-99ee-001122334455", "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", "rate": 2.0, "start_time": "2026-01-01T00:00:00Z", "end_time": "2026-12-31T23:59:59Z" }
Errors
400 — at query parameter is not a valid RFC 3339 timestamp.404 — no rate is effective for the resource at the supplied instant.A diff is an append-only audit record of a change applied to a compute allocation — for example a usage update or a status transition. Diffs are cascade-deleted when their parent allocation is deleted.
POST /compute-allocation-diffsRecord a new diff against a compute allocation.
Required fields: compute_allocation_id, diff_type, status Optional fields: id, new_su_amount (defaults to 0), timestamp (defaults to the server's current UTC time), description
diff_type is a free-form short code such as USAGE_UPDATE or ALLOCATION_STATUS_CHANGE. status must be one of ACTIVE, INACTIVE, DELETED.
Request
{ "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "diff_type": "USAGE_UPDATE", "new_su_amount": 90000, "status": "ACTIVE", "description": "Charged 10000 SUs for completed jobs" }
Response 201
{ "id": "44bb55cc-66dd-77ee-88ff-aabbccddeeff", "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "diff_type": "USAGE_UPDATE", "new_su_amount": 90000, "status": "ACTIVE", "timestamp": "2026-05-16T17:42:11.918Z", "description": "Charged 10000 SUs for completed jobs" }
Errors
400 — required field missing, or compute_allocation_id does not exist.GET /compute-allocation-diffs/{id}Retrieve a single diff by its ID.
Errors
404 — no diff matches the supplied ID.DELETE /compute-allocation-diffs/{id}Remove a diff record. Intended for administrative cleanup; diffs are otherwise append-only.
Response 204 — empty body on success.
GET /compute-allocations/{id}/diffsList every diff ever recorded against the given compute allocation, ordered by timestamp ascending.
Response 200
[ { "id": "44bb55cc-66dd-77ee-88ff-aabbccddeeff", "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "diff_type": "USAGE_UPDATE", "new_su_amount": 90000, "status": "ACTIVE", "timestamp": "2026-05-16T17:42:11.918Z", "description": "Charged 10000 SUs for completed jobs" } ]
GET /compute-allocations/{id}/diffs/latestReturn the most recent diff (highest timestamp) for the given allocation.
Errors
404 — the allocation has no diffs recorded.A change request represents a user- or admin-initiated proposal to mutate a compute allocation — e.g. asking for additional Service Units or to change its status. Each request carries a lifecycle (change_status: PENDING, APPROVED, REJECTED, etc.). Change requests are cascade-deleted when their parent allocation is deleted. Every create, update, and delete of a change request transactionally appends an entry to its event log (see below); the event log is intentionally not cascade-deleted so the audit trail survives the deletion of the parent change request.
POST /compute-allocation-change-requestsSubmit a new change request.
Required fields: compute_allocation_id, requester_id Optional fields: id, requested_su_amount, requested_status, reason, change_status (defaults to PENDING), approver_id, timestamp (defaults to the server's current UTC time)
Request
{ "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "requested_su_amount": 120000, "requested_status": "ACTIVE", "reason": "Need more SUs for upcoming HPC runs", "requester_id": "11112222-3333-4444-5555-666677778888" }
Response 201
{ "id": "9988aabb-ccdd-eeff-0011-223344556677", "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56", "requested_su_amount": 120000, "requested_status": "ACTIVE", "reason": "Need more SUs for upcoming HPC runs", "change_status": "PENDING", "requester_id": "11112222-3333-4444-5555-666677778888", "timestamp": "2026-05-16T17:42:11.918Z" }
Errors
400 — required field missing, or compute_allocation_id does not exist.GET /compute-allocation-change-requests/{id}Retrieve a single change request by its ID.
Errors
404 — no change request matches the supplied ID.PUT /compute-allocation-change-requests/{id}Replace mutable fields of a change request. Typically used by an approver to transition change_status (e.g. to APPROVED or REJECTED) and stamp approver_id. Omitted fields are preserved from the existing record.
Request
{ "change_status": "APPROVED", "approver_id": "aaaa-bbbb-cccc-dddd-eeee" }
Errors
400 — request id missing.404 — no change request matches the supplied ID.DELETE /compute-allocation-change-requests/{id}Remove a change request and (cascading) its event log.
Response 204 — empty body on success.
GET /compute-allocations/{id}/change-requestsList every change request ever submitted against the given allocation, ordered by timestamp ascending.
GET /users/{id}/change-requestsList every change request submitted by the given user, ordered by timestamp ascending.
Events are an append-only audit trail of state transitions applied to a change request — typically CREATED, APPROVED, REJECTED, UPDATED, DELETED, or arbitrary workflow markers. Create / update / delete of a change request each emit an event automatically; clients may also append custom events via the endpoint below. Events are not cascade-deleted when their parent change request is removed, so the audit trail is preserved indefinitely.
POST /compute-allocation-change-request-eventsAppend a new event to a change request.
Required fields: compute_allocation_change_request_id, event_type Optional fields: id, description, timestamp (defaults to the server's current UTC time)
Request
{ "compute_allocation_change_request_id": "9988aabb-ccdd-eeff-0011-223344556677", "event_type": "APPROVED", "description": "Change request approved by admin" }
Response 201
{ "id": "ee11ff22-3344-5566-7788-99aabbccddee", "compute_allocation_change_request_id": "9988aabb-ccdd-eeff-0011-223344556677", "event_type": "APPROVED", "description": "Change request approved by admin", "timestamp": "2026-05-16T18:00:00.000Z" }
Errors
400 — required field missing, or compute_allocation_change_request_id does not exist.GET /compute-allocation-change-request-events/{id}Retrieve a single event by its ID.
Errors
404 — no event matches the supplied ID.DELETE /compute-allocation-change-request-events/{id}Remove an event record. Intended for administrative cleanup; events are otherwise append-only.
Response 204 — empty body on success.
GET /compute-allocation-change-requests/{id}/eventsList every event recorded against the given change request, ordered by timestamp ascending.
GET /compute-allocation-change-requests/{id}/events/latestReturn the most recent event for the given change request.
Errors
404 — the change request has no events recorded.A ComputeAllocationMembership records a user's sub-allocation against a parent ComputeAllocation — i.e. how many SUs of the parent allocation the user is entitled to consume, and the time window plus lifecycle status of that grant. At most one membership can exist per (compute_allocation_id, user_id) pair (enforced by a unique key). Memberships are cascade-deleted when their parent allocation is removed.
/compute-allocation-membershipsCreate a new membership.
Request body
{ "compute_allocation_id": "alloc-123", "user_id": "user-456", "allocation_amount": 50000, "start_time": "2026-01-01T00:00:00Z", "end_time": "2026-12-31T23:59:59Z", "membership_status": "ACTIVE" }
compute_allocation_id and user_id are required and must reference existing rows.membership_status defaults to ACTIVE when omitted.id is generated server-side when omitted.Errors
400 — missing required fields, or referenced allocation/user not found.409 — a membership already exists for this (allocation, user) pair./compute-allocation-memberships/{id}Retrieve a membership by ID.
/compute-allocation-memberships/{id}Replace mutable fields of a membership. Fields left blank/zero in the request body fall back to the stored value (partial updates).
/compute-allocation-memberships/{id}/allocation-amountUpdate only the SU sub-allocation granted to the user.
Request body
{ "allocation_amount": 75000 }
Errors
400 — negative allocation_amount.404 — no membership with the given ID./compute-allocation-memberships/{id}/statusUpdate only the lifecycle status of the membership (ACTIVE, INACTIVE, DELETED, etc.).
Request body
{ "membership_status": "INACTIVE" }
Errors
400 — empty membership_status.404 — no membership with the given ID./compute-allocation-memberships/{id}Remove a membership.
/compute-allocations/{id}/membershipsList every membership recorded against the given allocation, ordered by start_time ascending.
/users/{id}/compute-allocation-membershipsList every allocation membership held by the given user, ordered by start_time ascending.
BASE=http://localhost:8080 ORG_ID=$(curl -s -X POST $BASE/organizations \ -H 'Content-Type: application/json' \ -d '{"name":"University of Example","originated_id":"ACCESS-ORG-001"}' \ | jq -r .id) USER_ID=$(curl -s -X POST $BASE/users \ -H 'Content-Type: application/json' \ -d "{\"organization_id\":\"$ORG_ID\",\"first_name\":\"Ada\",\"last_name\":\"Lovelace\",\"email\":\"ada@example.edu\"}" \ | jq -r .id) PROJ_ID=$(curl -s -X POST $BASE/projects \ -H 'Content-Type: application/json' \ -d "{\"title\":\"Climate Simulation 2026\",\"origination\":\"ACCESS\",\"originated_id\":\"ACCESS-PRJ-9000\",\"project_pi_id\":\"$USER_ID\"}" \ | jq -r .id) CLUSTER_ID=$(curl -s -X POST $BASE/compute-clusters \ -H 'Content-Type: application/json' \ -d '{"name":"Delta"}' | jq -r .id) ALLOC_ID=$(curl -s -X POST $BASE/compute-allocations \ -H 'Content-Type: application/json' \ -d "{\"project_id\":\"$PROJ_ID\",\"name\":\"Q2 2026 Climate Run\",\"compute_cluster_id\":\"$CLUSTER_ID\",\"initial_su_amount\":100000}" \ | jq -r .id) RES_ID=$(curl -s -X POST $BASE/compute-allocation-resources \ -H 'Content-Type: application/json' \ -d '{"name":"GPU B200","resource_type":"GPU","resource_amount":8}' | jq -r .id) # Attach the resource to the allocation. curl -s -X POST $BASE/compute-allocations/$ALLOC_ID/resources \ -H 'Content-Type: application/json' \ -d "{\"compute_allocation_resource_id\":\"$RES_ID\"}" | jq # Define a rate for the resource. curl -s -X POST $BASE/compute-allocation-resource-rates \ -H 'Content-Type: application/json' \ -d "{ \"compute_allocation_resource_id\":\"$RES_ID\", \"rate\":2.0, \"start_time\":\"2026-01-01T00:00:00Z\", \"end_time\":\"2026-12-31T23:59:59Z\" }" | jq # Look up the currently-effective rate. curl -s $BASE/compute-allocation-resources/$RES_ID/rates/effective | jq # Record a usage diff against the allocation. curl -s -X POST $BASE/compute-allocation-diffs \ -H 'Content-Type: application/json' \ -d "{ \"compute_allocation_id\":\"$ALLOC_ID\", \"diff_type\":\"USAGE_UPDATE\", \"new_su_amount\":90000, \"status\":\"ACTIVE\", \"description\":\"Charged 10000 SUs for completed jobs\" }" | jq # Inspect the diff history. curl -s $BASE/compute-allocations/$ALLOC_ID/diffs | jq curl -s $BASE/compute-allocations/$ALLOC_ID/diffs/latest | jq # Bidirectional lookups. curl -s $BASE/compute-allocations/$ALLOC_ID/resources | jq curl -s $BASE/compute-allocation-resources/$RES_ID/allocations | jq curl -s $BASE/projects/$PROJ_ID | jq
export DATABASE_DSN='custos:secret@tcp(127.0.0.1:3306)/custos?parseTime=true&charset=utf8mb4' # optional export HTTP_ADDR=:8080 export DB_MAX_OPEN_CONNS=25 export DB_MAX_IDLE_CONNS=5 go run ./cmd/server
| Environment variable | Default | Purpose |
|---|---|---|
DATABASE_DSN | (required) | MySQL/MariaDB DSN. parseTime=true is mandatory. |
HTTP_ADDR | :8080 | Address the HTTP server binds to. |
DB_MAX_OPEN_CONNS | 25 | Maximum open database connections. |
DB_MAX_IDLE_CONNS | 5 | Maximum idle database connections. |
Migrations from internal/db/migrations/ are applied automatically on startup.
The server handles SIGINT / SIGTERM gracefully, draining in-flight requests for up to 15 seconds before exiting.