{
  "openapi": "3.1.0",
  "info": {
    "title": "MExpense API",
    "version": "1.0.0",
    "description": "Personal transaction management. Generate a key in Developer API (/developer-api). One key per user; regeneration and deletion revoke the old key. Full guide: /api-docs. 120 authenticated requests/minute/user/server process. Amounts are decimal strings. Writes have no idempotency-key support; verify outcome before retrying uncertain writes."
  },
  "servers": [
    {
      "url": "/",
      "description": "This MExpense deployment"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/transactions": {
      "get": {
        "operationId": "listTransactions",
        "summary": "List your transactions",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "default": 0
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string",
              "enum": [
                "income",
                "expense",
                "transfer"
              ]
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "description": "Inclusive start date",
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "ISO timestamp with timezone and seconds, at most millisecond precision."
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "description": "Inclusive end date",
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "ISO timestamp with timezone and seconds, at most millisecond precision."
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Case-insensitive literal substring in note",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ]
      },
      "post": {
        "operationId": "createTransaction",
        "summary": "Create a transaction and atomically update balances",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Transaction"
                    }
                  }
                }
              }
            },
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Relative URL of the created transaction"
              }
            }
          },
          "413": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTransaction"
              }
            }
          }
        }
      }
    },
    "/api/v1/transactions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getTransaction",
        "summary": "Read your transaction",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Transaction"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateTransaction",
        "summary": "Partially update a transaction and atomically recalculate balances",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Transaction"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTransaction"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTransaction",
        "summary": "Delete a transaction and reverse its balance effect",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "204": {
            "description": "Deleted. No response body."
          },
          "404": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts": {
      "get": {
        "operationId": "listAccounts",
        "summary": "List your active accounts",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "default": 0
            }
          }
        ]
      }
    },
    "/api/v1/categories": {
      "get": {
        "operationId": "listCategories",
        "summary": "List your categories",
        "responses": {
          "400": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string",
                  "const": "Bearer"
                }
              }
            }
          },
          "403": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Category"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "default": 0
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string",
              "enum": [
                "income",
                "expense",
                "transfer"
              ]
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key generated on the Developer API page (/developer-api). Session cookies are not accepted."
      }
    },
    "schemas": {
      "Transaction": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense",
              "transfer"
            ]
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d{1,15}(\\.\\d{1,8})?$",
            "description": "Strictly positive decimal string; zero is rejected."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp with timezone and seconds, at most millisecond precision."
          },
          "fromAccountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "toAccountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "categoryId": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "type",
          "amount",
          "note",
          "date",
          "fromAccountId",
          "toAccountId",
          "categoryId",
          "id",
          "userId",
          "createdAt",
          "updatedAt"
        ]
      },
      "CreateTransaction": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "amount"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense",
              "transfer"
            ]
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d{1,15}(\\.\\d{1,8})?$",
            "description": "Strictly positive decimal string; zero is rejected.",
            "not": {
              "pattern": "^0+(\\.0+)?$"
            }
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp with timezone and seconds, at most millisecond precision."
          },
          "fromAccountId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          },
          "toAccountId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          },
          "categoryId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          }
        },
        "description": "Expense: source only. Income: destination only. Transfer: two distinct active accounts in the same currency. Category must match type and owner. Date defaults to now; optional fields default to null. Maximum body: 16 KiB."
      },
      "UpdateTransaction": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense",
              "transfer"
            ]
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d{1,15}(\\.\\d{1,8})?$",
            "description": "Strictly positive decimal string; zero is rejected.",
            "not": {
              "pattern": "^0+(\\.0+)?$"
            }
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp with timezone and seconds, at most millisecond precision."
          },
          "fromAccountId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          },
          "toAccountId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          },
          "categoryId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 200
          }
        },
        "description": "Omitted fields retain values. The merged transaction must satisfy all creation rules. Clear optional fields with null."
      },
      "Pagination": {
        "type": "object",
        "required": [
          "limit",
          "offset",
          "hasMore"
        ],
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "hasMore": {
            "type": "boolean"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "balance": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "icon": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "balance",
          "currency",
          "icon",
          "color"
        ]
      },
      "Category": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "icon": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "income",
              "expense",
              "transfer"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "icon",
          "color"
        ]
      }
    }
  },
  "externalDocs": {
    "url": "/api-docs",
    "description": "Full integration guide"
  }
}
