{
  "openapi": "3.0.3",
  "info": {
    "title": "Lakelyon Cloud API",
    "version": "1.0.0",
    "description": "Customer-facing developer API for Lakelyon Cloud. Authenticate with your session cookie (dashboard) or a developer API key sent as `Authorization: Bearer lky_live_...`. The first API release is read-only for keys; customer mutations stay session-bound.",
    "contact": { "name": "Lakelyon Cloud Support", "url": "https://cloud.lakelyon.com/dashboard/support" }
  },
  "servers": [
    { "url": "https://cloud.lakelyon.com", "description": "Production" },
    { "url": "http://localhost:3006", "description": "Local development" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Orders", "description": "The caller's own orders." },
    { "name": "Resources", "description": "Provisioned compute resources owned by the caller." },
    { "name": "Billing", "description": "Subscription, invoices and portal link." },
    { "name": "Profile", "description": "The caller's Lakelyon account." },
    { "name": "Webhooks", "description": "Outbound webhook endpoints and delivery log (session only)." },
    { "name": "Health", "description": "Liveness and readiness probes." }
  ],
  "paths": {
    "/api/orders": {
      "get": {
        "tags": ["Orders"],
        "summary": "List the caller's orders",
        "description": "Returns up to the 100 most recent orders with items, recent history and fulfillment. Accepts a session cookie or an API key with the `read:orders` scope.",
        "operationId": "listOrders",
        "responses": {
          "200": {
            "description": "Orders, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orders": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Order" }
                    }
                  },
                  "required": ["orders"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/orders/{id}": {
      "get": {
        "tags": ["Orders"],
        "summary": "Get one owned order",
        "description": "Returns a single order with items, 50 history entries and recent operations (provider errors sanitized). Returns 404 for orders that do not exist or are not owned by the caller.",
        "operationId": "getOrder",
        "parameters": [{ "$ref": "#/components/parameters/OrderId" }],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "order": { "$ref": "#/components/schemas/Order" } },
                  "required": ["order"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "tags": ["Orders"],
        "summary": "Cancel a pending order (session only)",
        "description": "Customer mutations are session-bound: API keys are read-only in the first API release. Only `PENDING_PAYMENT` orders can be cancelled by the customer.",
        "operationId": "cancelOrder",
        "parameters": [{ "$ref": "#/components/parameters/OrderId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": { "type": "string", "enum": ["cancel"] }
                },
                "required": ["action"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated order." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/resources": {
      "get": {
        "tags": ["Resources"],
        "summary": "List the caller's resources",
        "description": "Returns up to 200 owned resources, newest first. `providerStatus` is never returned. Accepts a session cookie or an API key with the `read:resources` scope.",
        "operationId": "listResources",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Optional free-text search over name, hostname and IPs (ownership scope unchanged).",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Resources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "q": { "type": ["string", "null"] },
                    "resources": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Resource" }
                    }
                  },
                  "required": ["resources"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/billing": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get billing summary",
        "description": "Returns the portal URL, active subscription (if any), the 20 most recent invoices and the current balance due. Accepts a session cookie or an API key with the `read:billing` scope.",
        "operationId": "getBilling",
        "responses": {
          "200": {
            "description": "Billing summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "portalUrl": { "type": "string" },
                    "subscription": {
                      "type": ["object", "null"],
                      "properties": {
                        "status": { "type": "string" },
                        "priceId": { "type": "string" },
                        "productId": { "type": "string" },
                        "interval": { "type": "string" },
                        "currentPeriodEnd": { "type": "string", "format": "date-time" },
                        "cancelAtPeriodEnd": { "type": "boolean" },
                        "productName": { "type": "string" }
                      }
                    },
                    "invoices": { "type": "array", "items": { "$ref": "#/components/schemas/Invoice" } },
                    "balanceDue": { "type": "integer" }
                  },
                  "required": ["portalUrl", "invoices", "balanceDue"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "503": { "description": "Customer portal is not available right now." }
        }
      }
    },
    "/api/profile": {
      "get": {
        "tags": ["Profile"],
        "summary": "Get the caller's profile",
        "description": "Returns the signed-in user's own profile (name, email, role, MFA status). Accepts a session cookie or an API key with the `read:profile` scope.",
        "operationId": "getProfile",
        "responses": {
          "200": {
            "description": "Profile.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Profile" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "patch": {
        "tags": ["Profile"],
        "summary": "Update the caller's profile (session only)",
        "description": "Profile mutations are session-bound — API keys are read-only.",
        "operationId": "updateProfile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "minLength": 1, "maxLength": 100 }
                },
                "required": ["name"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated profile.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Profile" } }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/profile/webhooks": {
      "get": {
        "tags": ["Webhooks"],
        "summary": "List webhook endpoints (session only)",
        "description": "Lists the customer's outbound webhook endpoints. Signing secrets are never returned on list.",
        "operationId": "listWebhookEndpoints",
        "responses": {
          "200": {
            "description": "Endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "endpoints": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookEndpoint" } }
                  },
                  "required": ["endpoints"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Webhooks"],
        "summary": "Create a webhook endpoint (session only)",
        "description": "Creates an endpoint. The signing `secret` is returned in plaintext exactly once — store it now.",
        "operationId": "createWebhookEndpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string", "maxLength": 2048 },
                  "eventTypes": { "type": "array", "items": { "type": "string" } },
                  "secret": { "type": "string", "description": "Optional customer-chosen signing secret; a random one is generated when omitted." }
                },
                "required": ["url"]
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Created. `{ endpoint, secret }` — the secret is shown only once." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/profile/webhooks/{id}": {
      "patch": {
        "tags": ["Webhooks"],
        "summary": "Update a webhook endpoint (session only)",
        "description": "Update url, eventTypes, enabled state or rotate the secret (`secret` in the body). A rotated secret is returned in plaintext exactly once.",
        "operationId": "updateWebhookEndpoint",
        "parameters": [{ "$ref": "#/components/parameters/EndpointId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string" },
                  "eventTypes": { "type": "array", "items": { "type": "string" } },
                  "secret": { "type": "string" },
                  "enabled": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated endpoint (rotated secret included when rotated)." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Webhooks"],
        "summary": "Delete a webhook endpoint (session only)",
        "operationId": "deleteWebhookEndpoint",
        "parameters": [{ "$ref": "#/components/parameters/EndpointId" }],
        "responses": {
          "200": { "description": "Deleted." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/profile/webhooks/{id}/deliveries": {
      "get": {
        "tags": ["Webhooks"],
        "summary": "Get the delivery log for an endpoint (session only)",
        "operationId": "listWebhookDeliveries",
        "parameters": [{ "$ref": "#/components/parameters/EndpointId" }],
        "responses": {
          "200": { "description": "Deliveries for the endpoint." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Liveness probe",
        "description": "Always returns 200 when the process is alive. No external probes.",
        "operationId": "liveness",
        "security": [],
        "responses": {
          "200": {
            "description": "Process is alive.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string" },
                    "uptimeSeconds": { "type": "number" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/health/ready": {
      "get": {
        "tags": ["Health"],
        "summary": "Readiness probe",
        "description": "200 when the instance may serve traffic (database up; payment/provider issues reported as degraded, not fatal). 503 when it should not receive traffic.",
        "operationId": "readiness",
        "security": [],
        "responses": {
          "200": { "description": "Ready to serve traffic." },
          "503": { "description": "Not ready." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "developer API key (lky_live_...)",
        "description": "Send `Authorization: Bearer <key>` with a developer API key, or use the dashboard session cookie. Keys are scoped (read-only in the first API release)."
      }
    },
    "parameters": {
      "OrderId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Order ID."
      },
      "EndpointId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Webhook endpoint ID."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credentials (401).",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "Forbidden": {
        "description": "Authenticated but the credential's scope does not allow this operation (403), or the API-key feature is not enabled for the account.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "BadRequest": {
        "description": "Invalid request body or parameters (400).",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "NotFound": {
        "description": "Resource does not exist or is not owned by the caller (404).",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message." }
        },
        "required": ["error"]
      },
      "Order": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "orderNumber": { "type": "string" },
          "status": { "type": "string", "enum": ["PENDING_PAYMENT", "PAID", "ACTIVE", "CANCELED", "FULFILLED", "FAILED"] },
          "stripeTestMode": { "type": "boolean" },
          "currency": { "type": "string" },
          "subtotalMinor": { "type": "integer", "description": "Amount in minor currency units (e.g. cents)." },
          "taxMinor": { "type": "integer" },
          "totalMinor": { "type": "integer" },
          "configurationJson": { "type": "string" },
          "stripeSubscriptionId": { "type": ["string", "null"] },
          "paidAt": { "type": ["string", "null"], "format": "date-time" },
          "fulfilledAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "checkoutAttempt": { "type": ["string", "null"] },
          "lastCheckoutError": { "type": ["string", "null"] },
          "items": { "type": "array", "items": { "type": "object" } },
          "history": { "type": "array", "items": { "type": "object" } },
          "fulfillment": { "type": ["object", "null"] }
        },
        "required": ["id", "orderNumber", "status", "currency", "totalMinor", "createdAt"]
      },
      "Resource": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "displayName": { "type": "string" },
          "hostname": { "type": ["string", "null"] },
          "publicIps": { "type": "array", "items": { "type": "string" } },
          "region": { "type": "string" },
          "image": { "type": "string" },
          "specSnapshotJson": { "type": "string" },
          "status": { "type": "string" },
          "lastSyncedAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "orderItem": {
            "type": ["object", "null"],
            "properties": {
              "orderId": { "type": "string" },
              "productName": { "type": "string" },
              "variantName": { "type": "string" },
              "unitAmountMinor": { "type": "integer" },
              "currency": { "type": "string" }
            }
          }
        },
        "required": ["id", "displayName", "status", "createdAt"]
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "amountDue": { "type": "integer" },
          "amountPaid": { "type": "integer" },
          "currency": { "type": "string" },
          "status": { "type": "string" },
          "hostedInvoiceUrl": { "type": ["string", "null"] },
          "invoicePdf": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "amountDue", "currency", "status", "createdAt"]
      },
      "Profile": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "email": { "type": "string" },
          "role": { "type": "string" },
          "mfaEnabled": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "email", "role"]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "url": { "type": "string" },
          "eventTypes": { "type": "array", "items": { "type": "string" } },
          "enabled": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "url", "eventTypes", "enabled"]
      }
    }
  }
}
