{
  "openapi": "3.1.0",
  "info": {
    "title": "Paidwell API",
    "version": "1.0.0",
    "description": "Programmatic access to Paidwell, a personal consulting time tracker. Authenticate every request with a Paidwell API key (`paidwell_sk_…`) via `Authorization: Bearer <key>` or the `X-API-Key` header."
  },
  "servers": [
    {
      "url": "{baseUrl}",
      "description": "Your Paidwell deployment",
      "variables": {
        "baseUrl": {
          "default": "https://your-paidwell-host.example.com",
          "description": "Replace with your Paidwell base URL (e.g. your Railway domain)."
        }
      }
    }
  ],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Authorization: Bearer paidwell_sk_…" },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": { "code": { "type": "string" }, "message": { "type": "string" } }
          }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "client": { "type": ["string", "null"] },
          "hourlyRate": { "type": ["number", "null"] },
          "color": { "type": ["string", "null"] },
          "archived": { "type": "boolean" },
          "callMinimumMinutes": { "type": ["integer", "null"] },
          "callPaddingMinutes": { "type": ["integer", "null"] },
          "roundToMinutes": { "type": ["integer", "null"] },
          "createdAt": { "type": "string" }
        }
      },
      "TimeEntry": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "projectId": { "type": "string" },
          "projectName": { "type": "string" },
          "date": { "type": "string", "description": "YYYY-MM-DD" },
          "durationMinutes": { "type": "integer" },
          "description": { "type": "string" },
          "billable": { "type": "boolean" },
          "billed": { "type": "boolean" },
          "invoiceRef": { "type": ["string", "null"] },
          "isCall": { "type": "boolean" },
          "bookedDurationMinutes": { "type": ["integer", "null"] },
          "createdAt": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/api/v1/ping": {
      "get": {
        "operationId": "ping",
        "summary": "Verify the API key works",
        "responses": {
          "200": { "description": "Authenticated", "content": { "application/json": { "schema": { "type": "object" } } } },
          "401": { "description": "Missing/invalid key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List projects",
        "parameters": [
          { "name": "archived", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] }, "description": "Filter by archived status" }
        ],
        "responses": {
          "200": {
            "description": "Projects",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "projects": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } } } } }
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "summary": "Create a project",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string" },
                  "client": { "type": "string" },
                  "color": { "type": "string", "description": "Hex color, defaults to #3a9a8c" },
                  "hourlyRate": { "type": "number" },
                  "callMinimumMinutes": { "type": "integer", "description": "Minimum billed minutes for a call" },
                  "callPaddingMinutes": { "type": "integer", "description": "Prep+post minutes added to calls" },
                  "roundToMinutes": { "type": "integer", "description": "Round call duration to nearest N minutes" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "project": { "$ref": "#/components/schemas/Project" } } } } } },
          "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/entries": {
      "get": {
        "operationId": "listEntries",
        "summary": "List time entries",
        "parameters": [
          { "name": "projectId", "in": "query", "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string" }, "description": "YYYY-MM-DD (inclusive)" },
          { "name": "to", "in": "query", "schema": { "type": "string" }, "description": "YYYY-MM-DD (inclusive)" },
          { "name": "billable", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "billed", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Substring match on description" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100, "maximum": 1000 } }
        ],
        "responses": {
          "200": { "description": "Entries", "content": { "application/json": { "schema": { "type": "object", "properties": { "entries": { "type": "array", "items": { "$ref": "#/components/schemas/TimeEntry" } }, "count": { "type": "integer" } } } } } }
        }
      },
      "post": {
        "operationId": "logTime",
        "summary": "Log a time entry",
        "description": "Specify the project by `projectId` or by `project` name. Specify duration by `durationMinutes`, `duration` (e.g. \"1h30m\", \"1.5\", \"2:30\"), or set `isCall: true` with `bookedMinutes`/`bookedDuration` to apply the project's call-billing rules.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["description"],
                "properties": {
                  "projectId": { "type": "string" },
                  "project": { "type": "string", "description": "Project name (case-insensitive) — alternative to projectId" },
                  "description": { "type": "string" },
                  "date": { "type": "string", "description": "YYYY-MM-DD; defaults to server-local today" },
                  "durationMinutes": { "type": "integer" },
                  "duration": { "type": "string", "description": "Human duration, e.g. \"1h30m\", \"1.5\", \"90m\", \"2:30\"" },
                  "isCall": { "type": "boolean" },
                  "bookedMinutes": { "type": "integer", "description": "Actual call length in minutes (call mode)" },
                  "bookedDuration": { "type": "string", "description": "Actual call length as a string (call mode)" },
                  "billable": { "type": "boolean", "default": true },
                  "billed": { "type": "boolean", "default": false },
                  "invoiceRef": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Logged", "content": { "application/json": { "schema": { "type": "object", "properties": { "entry": { "$ref": "#/components/schemas/TimeEntry" }, "summary": { "type": "string" } } } } } },
          "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/entries/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "get": {
        "operationId": "getEntry",
        "summary": "Get one time entry",
        "responses": {
          "200": { "description": "Entry", "content": { "application/json": { "schema": { "type": "object", "properties": { "entry": { "$ref": "#/components/schemas/TimeEntry" } } } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "patch": {
        "operationId": "updateEntry",
        "summary": "Update a time entry",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": { "type": "string" },
                  "date": { "type": "string" },
                  "durationMinutes": { "type": "integer" },
                  "billable": { "type": "boolean" },
                  "billed": { "type": "boolean" },
                  "invoiceRef": { "type": ["string", "null"], "description": "Send null to clear" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated", "content": { "application/json": { "schema": { "type": "object", "properties": { "entry": { "$ref": "#/components/schemas/TimeEntry" } } } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "operationId": "deleteEntry",
        "summary": "Delete a time entry",
        "responses": {
          "200": { "description": "Deleted", "content": { "application/json": { "schema": { "type": "object" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/summary": {
      "get": {
        "operationId": "getSummary",
        "summary": "Hours + revenue rollup",
        "parameters": [
          { "name": "projectId", "in": "query", "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string" }, "description": "YYYY-MM-DD" },
          { "name": "to", "in": "query", "schema": { "type": "string" }, "description": "YYYY-MM-DD" }
        ],
        "responses": {
          "200": {
            "description": "Rollup",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "totals": { "type": "object" }, "byProject": { "type": "array", "items": { "type": "object" } } } } } }
          }
        }
      }
    }
  }
}
