{
  "openapi": "3.1.0",
  "info": {
    "title": "Scadenzario API",
    "version": "1.0.0",
    "description": "Personal deadlines for scripts and automations (n8n, Make, custom integrations). Same bearer token and same plan rule as the MCP server: available on every plan within the cloud quota (20 active deadlines on Free, 5000 on PRO). Personal deadlines only; recurring completion and recurrence edits are app-only in v1.",
    "contact": { "url": "https://www.scadenzario.app/api" }
  },
  "servers": [{ "url": "https://www.scadenzario.app/api/v1" }],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal API/MCP token generated in the app (Settings > Analyze with AI). Never in query strings."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "enum": ["unauthenticated", "forbidden", "invalid_input", "not_found", "unsupported_for_recurring", "sync_limit_reached", "rate_limited", "internal"]
              },
              "message": { "type": "string" },
              "details": { "type": "object" }
            }
          }
        }
      },
      "Deadline": {
        "type": "object",
        "required": ["id", "title", "due_at", "recurrence", "is_done"],
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "due_at": { "type": "string", "format": "date-time" },
          "notes": { "type": ["string", "null"] },
          "emoji": { "type": ["string", "null"] },
          "recurrence": { "type": "string", "enum": ["none", "daily", "weekly", "monthly", "yearly", "multiple_daily", "interval", "specific_dates", "yearly_dates", "quarterly", "bimonthly", "biennial", "semiannual"] },
          "custom_interval": { "type": "integer" },
          "reminder_days": { "type": "array", "items": { "type": "integer" } },
          "notification_times": { "type": "array", "items": { "type": "string" } },
          "amount": { "type": ["number", "null"] },
          "transaction_type": { "type": "string", "enum": ["none", "expense", "income"] },
          "is_paid": { "type": "boolean" },
          "paid_at": { "type": ["string", "null"], "format": "date-time" },
          "paid_amount": { "type": ["number", "null"], "description": "Amount actually paid, when it differs from amount." },
          "is_done": { "type": "boolean" },
          "label_id": { "type": ["string", "null"] },
          "label_ids": { "type": "array", "items": { "type": "string" } },
          "has_attachment": { "type": "boolean" },
          "created_at": { "type": ["string", "null"] },
          "updated_at": { "type": ["string", "null"] }
        }
      },
      "Label": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" }
        }
      },
      "CreateDeadlineInput": {
        "type": "object",
        "required": ["title", "due_at"],
        "properties": {
          "title": { "type": "string" },
          "due_at": { "type": "string", "description": "ISO date (treated as that morning) or full ISO date-time." },
          "notes": { "type": "string" },
          "emoji": { "type": "string" },
          "recurrence": { "type": "string", "enum": ["none", "daily", "weekly", "monthly", "yearly", "multiple_daily", "interval", "quarterly", "bimonthly", "biennial", "semiannual"] },
          "custom_interval": { "type": "integer", "minimum": 1 },
          "reminder_days": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 365 } },
          "notification_times": { "type": "array", "items": { "type": "string" } },
          "amount": { "type": "number" },
          "transaction_type": { "type": "string", "enum": ["none", "expense", "income"] },
          "label_id": { "type": "string" },
          "label_ids": { "type": "array", "items": { "type": "string" }, "maxItems": 100 },
          "time_zone": { "type": "string", "description": "IANA zone for the civil due date. Defaults to the digest preference, else Europe/Rome." },
          "client_request_id": { "type": "string", "description": "Stable key making retries idempotent. The Idempotency-Key header fills this automatically." }
        }
      },
      "UpdateDeadlinePatch": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "title": { "type": "string" },
          "notes": { "type": ["string", "null"] },
          "emoji": { "type": ["string", "null"] },
          "due_at": { "type": "string" },
          "time_zone": { "type": "string" },
          "amount": { "type": ["number", "null"] },
          "transaction_type": { "type": "string", "enum": ["none", "expense", "income"] },
          "is_paid": { "type": "boolean" },
          "label_id": { "type": "string" },
          "label_ids": { "type": "array", "items": { "type": "string" }, "maxItems": 100 },
          "reminder_days": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 365 } },
          "notification_times": { "type": "array", "items": { "type": "string" } }
        }
      },
      "BatchCreateInput": {
        "type": "object",
        "required": ["deadlines"],
        "properties": {
          "deadlines": { "type": "array", "items": { "$ref": "#/components/schemas/CreateDeadlineInput" }, "minItems": 1, "maxItems": 20 }
        }
      },
      "BatchCreateOutcome": {
        "type": "object",
        "required": ["created", "failed"],
        "properties": {
          "created": { "type": "array", "items": { "$ref": "#/components/schemas/Deadline" } },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["index", "reason"],
              "properties": {
                "index": { "type": "integer" },
                "client_request_id": { "type": "string" },
                "reason": { "type": "string" }
              }
            }
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": ["plan", "active_count", "limit"],
        "properties": {
          "plan": { "type": "string", "enum": ["free", "pro"] },
          "active_count": { "type": "integer" },
          "limit": { "type": "integer" }
        }
      },
      "DeadlinePage": {
        "type": "object",
        "required": ["data", "next_cursor", "truncated"],
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Deadline" } },
          "next_cursor": { "type": ["string", "null"], "description": "Pass back as cursor for the next page; null at the end." },
          "truncated": { "type": "boolean", "description": "True when the scan hit its cap before filling the page: keep going with next_cursor." }
        }
      },
      "Summary": {
        "type": "object",
        "required": ["overdue", "next_7_days", "next_30_days", "unpaid_amount"],
        "properties": {
          "overdue": { "type": "object", "required": ["count", "amount"], "properties": { "count": { "type": "integer" }, "amount": { "type": "number" } } },
          "next_7_days": { "type": "object", "required": ["count", "amount"], "properties": { "count": { "type": "integer" }, "amount": { "type": "number" } } },
          "next_30_days": { "type": "object", "required": ["count", "amount"], "properties": { "count": { "type": "integer" }, "amount": { "type": "number" } } },
          "unpaid_amount": { "type": "number" }
        }
      }
    }
  },
  "paths": {
    "/deadlines": {
      "get": {
        "summary": "List personal deadlines",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active", "completed", "all"], "default": "active" } },
          { "name": "from", "in": "query", "schema": { "type": "string" } },
          { "name": "to", "in": "query", "schema": { "type": "string" } },
          { "name": "q", "in": "query", "schema": { "type": "string" } },
          { "name": "label_id", "in": "query", "explode": true, "schema": { "type": "array", "items": { "type": "string" } }, "description": "Repeatable (OR)." },
          { "name": "paid", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "recurring", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "overdue", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "next_cursor from a previous page." }
        ],
        "responses": {
          "200": { "description": "One page of matching deadlines.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeadlinePage" } } } },
          "400": { "description": "Invalid filter or cursor.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited (60 req/min).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "summary": "Create one deadline",
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Fills client_request_id: retrying with the same key returns the original deadline." }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateDeadlineInput" } } } },
        "responses": {
          "201": { "description": "Created. Location points at the new resource.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "400": { "description": "Invalid input.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Plan limit reached (details: count, limit).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited (60 req/min).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/batch": {
      "post": {
        "summary": "Create up to 20 deadlines",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchCreateInput" } } } },
        "responses": {
          "200": { "description": "Partial success is normal: check created and failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchCreateOutcome" } } } },
          "400": { "description": "Invalid input.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "PRO required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "get": {
        "summary": "Get one deadline",
        "responses": {
          "200": { "description": "The deadline.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "patch": {
        "summary": "Update one deadline",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateDeadlinePatch" } } } },
        "responses": {
          "200": { "description": "The updated deadline.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "400": { "description": "Invalid input.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Recurring date change (unsupported_for_recurring) or plan limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "summary": "Delete one deadline",
        "description": "Writes the sync tombstone so every device drops it. Deleting the stored occurrence of a recurring deadline ends the series. Storage attachments are left alone.",
        "responses": {
          "204": { "description": "Deleted." },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/{id}/complete": {
      "post": {
        "summary": "Complete one non-recurring deadline",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "The completed deadline (idempotent).", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Recurring deadlines must be completed in the app.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/{id}/reopen": {
      "post": {
        "summary": "Reopen one non-recurring deadline",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "The reopened deadline.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Recurring deadline, or reopening would exceed the plan limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/{id}/pay": {
      "post": {
        "summary": "Record a payment without completing",
        "description": "Paid and completed stay distinct; allowed on recurring deadlines (paying one instalment never closes the series).",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "paid_at": { "type": "string", "format": "date-time" }, "paid_amount": { "type": "number", "description": "Amount actually paid, when it differs from the deadline amount." } } } } } },
        "responses": {
          "200": { "description": "The paid deadline.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "400": { "description": "Invalid input.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/deadlines/{id}/unpay": {
      "post": {
        "summary": "Clear the payment state",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "The unpaid deadline.", "content": { "application/json": { "schema": { "type": "object", "required": ["deadline"], "properties": { "deadline": { "$ref": "#/components/schemas/Deadline" } } } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown or deleted id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/labels": {
      "get": {
        "summary": "List labels",
        "responses": {
          "200": { "description": "The user's labels.", "content": { "application/json": { "schema": { "type": "object", "required": ["labels"], "properties": { "labels": { "type": "array", "items": { "$ref": "#/components/schemas/Label" } } } } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "summary": "Create a label",
        "description": "A duplicate name (case-insensitive) returns the existing label.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "maxLength": 100 } } } } },
        "responses": {
          "201": { "description": "Created (or the existing duplicate).", "content": { "application/json": { "schema": { "type": "object", "required": ["label"], "properties": { "label": { "$ref": "#/components/schemas/Label" } } } } } } },
          "400": { "description": "Invalid input.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Missing scope (token without write access).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/summary": {
      "get": {
        "summary": "Summarise what is owed",
        "description": "Totals over open deadlines only: overdue, due in 7/30 days, unpaid total.",
        "responses": {
          "200": { "description": "The summary.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Summary" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Check the token and quota",
        "description": "Handy first call from n8n or Make to verify the key and see plan usage.",
        "responses": {
          "200": { "description": "Plan and active usage.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Usage" } } } },
          "401": { "description": "Missing or invalid token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  }
}
