# MiniDiscovery API Documentation This document describes the HTTP API endpoints provided by MiniDiscovery. ## Authentication Most API endpoints require authentication using an API token. Tokens must be passed in the `X-API-Token` HTTP header. ```http GET /v1/catalog/services HTTP/1.1 Host: your-minidiscovery-host:8500 X-API-Token: your_secret_api_token ``` Tokens have associated permissions: * **`read`**: Allows reading service information (catalog, health). * **`write`**: Allows registering and deregistering services. * **`admin`**: Allows all `read` and `write` operations, plus managing ACL tokens. The initial admin token is set using the `MINIDISCOVERY_ADMIN_TOKEN` environment variable on the *first* run when the database is empty. Subsequent tokens are managed via the API. ## Data Models ### `ServiceInstance` This model represents a single instance of a registered service. ```json { "id": "web-instance-1", "name": "web", "address": "192.168.1.100", "port": 80, "tags": ["frontend", "nginx"], "metadata": { "version": "1.2.3", "region": "us-east-1" }, "health": "passing", "last_updated": 1678886400.123456 } ``` * `id` (string, required): Unique identifier for this specific instance. * `name` (string, required): Logical name of the service (e.g., 'web', 'api', 'db'). * `address` (string, required): IP address or resolvable hostname where the service listens. * `port` (integer, required): Port number (1-65535). * `tags` (array of strings, optional): List of tags for filtering/grouping. Defaults to `[]`. * `metadata` (object, optional): Key-value string pairs for arbitrary metadata. Defaults to `{}`. * `health` (string, optional): Current health status ('passing', 'failing', 'unknown'). Defaults to 'passing' on registration. Automatically updated by the health checker. * `last_updated` (float, internal): Unix timestamp of the last update (set automatically). ## Endpoints ### Agent Endpoints These endpoints interact with the local agent state (registering/deregistering services). #### Register Service * **`POST /v1/agent/service/register`** * **Description:** Registers a new service instance or updates an existing one with the same `id`. This operation is idempotent based on the service `id`. * **Authentication:** Requires `write` permission. * **Request Body:** `ServiceInstance` JSON object. The `health` and `last_updated` fields in the request body are ignored; health is managed internally, and `last_updated` is set automatically. * **Success Response:** `200 OK` ```json { "status": "registered", "service_id": "web-instance-1" } ``` * **Error Responses:** * `400 Bad Request`: Invalid request body format. * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions. * `500 Internal Server Error`: Database error during registration. * **`curl` Example:** ```bash curl -X POST http://localhost:8500/v1/agent/service/register \ -H "X-API-Token: your_write_token" \ -H "Content-Type: application/json" \ -d '{ "id": "api-instance-01", "name": "api", "address": "10.0.1.5", "port": 8080, "tags": ["backend", "v2"], "metadata": {"git_sha": "a1b2c3d"} }' ``` #### Deregister Service * **`PUT /v1/agent/service/deregister/{service_id}`** *(Note: Consul uses PUT here, although DELETE might feel more intuitive)* * **Description:** Removes a service instance by its ID. * **Authentication:** Requires `write` permission. * **Path Parameters:** * `service_id` (string): The unique ID of the service instance to deregister. * **Success Response:** `200 OK` ```json { "status": "deregistered", "service_id": "api-instance-01" } ``` * **Error Responses:** * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions. * `404 Not Found`: Service with the given `service_id` does not exist. * **`curl` Example:** ```bash curl -X PUT http://localhost:8500/v1/agent/service/deregister/api-instance-01 \ -H "X-API-Token: your_write_token" ``` ### Catalog Endpoints These endpoints provide information about registered services across the system. #### List Services * **`GET /v1/catalog/services`** * **Description:** Returns a map of all registered service names to a list of unique tags associated with instances of that service. * **Authentication:** Requires `read` permission. * **Success Response:** `200 OK` ```json { "web": ["frontend", "nginx"], "api": ["backend", "v2"], "db": [] } ``` * **Error Responses:** * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions. * **`curl` Example:** ```bash curl http://localhost:8500/v1/catalog/services \ -H "X-API-Token: your_read_token" ``` #### List Service Instances * **`GET /v1/catalog/service/{service_name}`** * **Description:** Returns a list of all registered instances for a given service name. * **Authentication:** Requires `read` permission. * **Path Parameters:** * `service_name` (string): The name of the service (e.g., 'web', 'api'). * **Query Parameters:** * `tag` (string, optional): Filter instances by tag. Only instances with this tag will be returned. * **Success Response:** `200 OK` (Returns an array of `ServiceInstance` objects) ```json [ { "id": "web-instance-1", "name": "web", "address": "192.168.1.100", "port": 80, "tags": ["frontend", "nginx"], "metadata": {"version": "1.2.3"}, "health": "passing", "last_updated": 1678886400.123 }, { "id": "web-instance-2", "name": "web", "address": "192.168.1.101", "port": 80, "tags": ["frontend"], "metadata": {"version": "1.2.4"}, "health": "failing", "last_updated": 1678886405.456 } ] ``` * Returns `[]` if the service name is not found. * **Error Responses:** * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions. * **`curl` Examples:** ```bash # Get all 'web' instances curl http://localhost:8500/v1/catalog/service/web \ -H "X-API-Token: your_read_token" # Get 'web' instances tagged with 'nginx' curl http://localhost:8500/v1/catalog/service/web?tag=nginx \ -H "X-API-Token: your_read_token" ``` ### Health Endpoints These endpoints provide service discovery filtered by health status. #### List Healthy Service Instances * **`GET /v1/health/service/{service_name}`** * **Description:** Returns a list of service instances, similar to the catalog endpoint, but allows filtering by health status. * **Authentication:** Requires `read` permission. * **Path Parameters:** * `service_name` (string): The name of the service. * **Query Parameters:** * `tag` (string, optional): Filter instances by tag. * `passing` (boolean, optional): If `true`, only return instances with a `passing` health status. Defaults to `false` (returns all instances regardless of health). * **Success Response:** `200 OK` (Returns an array of `ServiceInstance` objects) * Response format is identical to `/v1/catalog/service/{service_name}` but filtered according to query parameters. * **Error Responses:** * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions. * **`curl` Examples:** ```bash # Get all 'api' instances (healthy or not) curl http://localhost:8500/v1/health/service/api \ -H "X-API-Token: your_read_token" # Get only healthy ('passing') 'api' instances curl http://localhost:8500/v1/health/service/api?passing=true \ -H "X-API-Token: your_read_token" # Get only healthy 'api' instances tagged 'v2' curl 'http://localhost:8500/v1/health/service/api?passing=true&tag=v2' \ -H "X-API-Token: your_read_token" ``` ### ACL Token Endpoints These endpoints manage API access tokens. #### Create Token * **`POST /v1/acl/token`** * **Description:** Creates a new API token with specified permissions. * **Authentication:** Requires `admin` permission. * **Request Body:** ```json { "name": "my-app-token", "permissions": ["read", "write"] } ``` * `name` (string, required): A unique descriptive name for the token. * `permissions` (array of strings, optional): List of permissions (`read`, `write`, `admin`). Defaults to `["read", "write"]`. * **Success Response:** `201 Created` ```json { "token": "YOUR_NEW_SECRET_TOKEN_VALUE", "name": "my-app-token", "permissions": ["read", "write"] } ``` * **IMPORTANT:** The `token` value is the actual secret token. It is **only shown once** in this response. Store it securely immediately. * **Error Responses:** * `400 Bad Request`: Invalid request body or token name already exists. * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions (not admin). * `500 Internal Server Error`: Database error during token creation. * **`curl` Example:** ```bash curl -X POST http://localhost:8500/v1/acl/token \ -H "X-API-Token: your_admin_token" \ -H "Content-Type: application/json" \ -d '{ "name": "read-only-dashboard", "permissions": ["read"] }' ``` #### Revoke Token * **`DELETE /v1/acl/token/{token_to_revoke}`** * **Description:** Revokes (deletes) an existing API token. * **Authentication:** Requires `admin` permission. * **Path Parameters:** * `token_to_revoke` (string): The **actual secret token value** of the token you want to revoke. * **Success Response:** `200 OK` ```json { "status": "revoked", "token_info": "Token revoked successfully" } ``` * **Error Responses:** * `401 Unauthorized`: Missing `X-API-Token`. * `403 Forbidden`: Invalid token or insufficient permissions (not admin). * `404 Not Found`: The token specified in the path does not exist or was already revoked. * **`curl` Example:** ```bash # Replace YOUR_OLD_SECRET_TOKEN_VALUE with the token to be deleted curl -X DELETE http://localhost:8500/v1/acl/token/YOUR_OLD_SECRET_TOKEN_VALUE \ -H "X-API-Token: your_admin_token" ``` ### DNS over HTTPS (DoH) * **`GET /dns-query`** * **Description:** Provides a simplified DNS-over-HTTPS interface (RFC 8484 GET format) for querying service addresses and SRV records. Primarily intended for simple DNS clients or scripts. **Note:** Requires service names to end with the configured DNS suffix (default: `.laiska.local`). * **Authentication:** None (typically). * **Query Parameters:** * `name` (string, required): The DNS query name (e.g., `web.laiska.local`, `_frontend._tcp.web.laiska.local`). * `type` (string, optional): The DNS record type (e.g., `A`, `SRV`, `TXT`). Defaults to `A`. Case-insensitive. * **Success Response:** `200 OK` (JSON object following RFC 8484 structure) * **Example (A query for `web.laiska.local`):** ```json { "Status": 0, "TC": false, "RD": true, "RA": false, "AD": false, "CD": false, "Question": [{"name": "web.laiska.local.", "type": 1}], "Answer": [ {"name": "web.laiska.local.", "type": 1, "TTL": 60, "data": "192.168.1.100"}, {"name": "web.laiska.local.", "type": 1, "TTL": 60, "data": "192.168.1.101"} ] } ``` * **Example (SRV query for `_frontend._tcp.web.laiska.local`):** ```json { "Status": 0, "TC": false, "RD": true, "RA": false, "AD": false, "CD": false, "Question": [{"name": "_frontend._tcp.web.laiska.local.", "type": 33}], "Answer": [ {"name": "_frontend._tcp.web.laiska.local.", "type": 33, "TTL": 60, "data": "0 10 80 web-instance-1.laiska.local."}, {"name": "_frontend._tcp.web.laiska.local.", "type": 33, "TTL": 60, "data": "0 10 80 web-instance-2.laiska.local."} ], "Additional": [ // Note: MiniDiscovery doesn't currently populate Additional well for DoH // Ideally A records for targets would be here ] } ``` * **Error/NXDOMAIN Response:** Returns JSON with `"Status"` other than `0` (e.g., `2` for NXDOMAIN, `4` for Not Implemented type). * **`curl` Example:** ```bash # Query for A records curl "http://localhost:8500/dns-query?name=web.laiska.local&type=A" # Query for SRV records curl "http://localhost:8500/dns-query?name=_frontend._tcp.web.laiska.local&type=SRV" ``` ### Other Endpoints #### Root * **`GET /`** * **Description:** Provides a basic entry point. Redirects browsers to `/status`. API clients receive a simple JSON message. * **Authentication:** None. * **Response (Browser):** HTTP 307 Redirect to `/status`. * **Response (API Client):** `200 OK` ```json { "message": "MiniDiscovery API Root. See /status for HTML view or /docs for API documentation." } ``` #### Status Page * **`GET /status`** * **Description:** Serves a simple HTML status page showing node health based on registered services. Useful for a quick visual overview. Automatically refreshes. * **Authentication:** None. * **Response:** `200 OK` with `Content-Type: text/html`.