{
    "openapi": "3.0.3",
    "info": {
        "title": "Debt Tracker REST API",
        "version": "1.0.0",
        "description": "Read and write **your own** debt data over HTTP: creditors (credit cards, charge cards and loans), your saved payoff plan, and your recurring annual windfalls.\n\n## Your data stays yours\n\n* Every request is made **as you** and can only ever see or change your own data. Nobody else's data can be reached through this API, and administrators get no exception.\n* API access is included with **Lifetime** membership.\n* **Personal API tokens** (recommended for scripts, spreadsheets and AI assistants): create one at [/account/api-tokens](https://debtsunset.com/account/api-tokens) and send it as `Authorization: Bearer dst_…`. Tokens are stored only as a hash, are shown once, and can be revoked at any time. No CSRF header is needed.\n* Alternatively, a **login session** (cookie), for accounts that have a password; changes (`POST`, `PATCH`, `DELETE`) then also need the session's **CSRF token** in an `X-CSRF-Token` header. HTTP Basic authentication is not accepted.\n* Your data is stored encrypted, is never shown in the site's content lists, and every response is sent `Cache-Control: private, no-store`.\n\n## Quick start\n\n1. **Create a token** at [/account/api-tokens](https://debtsunset.com/account/api-tokens).\n2. **Look up a card** with `GET /api/debt-tracker/cards` and note its `card_tid`.\n3. **Create a creditor** with `POST /api/debt-tracker/creditors` (example below).\n4. **Change it** with `PATCH /api/debt-tracker/creditors/{id}`, sending only what changed.\n\n```bash\n# 1. your token from /account/api-tokens\nTOKEN='dst_...'\n\n# 2. list the cards you can base a creditor on\ncurl -sS -H \"Authorization: Bearer $TOKEN\" 'https://debtsunset.com/api/debt-tracker/cards?_format=json'\n\n# 3. create a creditor from a listed card\ncurl -sS -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' \\\n  -d '{\"card_tid\":215,\"balance_due\":2450.75,\"apr\":24.99,\"billing_cycle_start_date\":\"2026-09-05\",\"due_date\":\"2026-10-27\"}' \\\n  'https://debtsunset.com/api/debt-tracker/creditors?_format=json'\n```\n\n## Conventions\n\n* Add **`?_format=json`** to every request and send `Content-Type: application/json` with a body. Other formats are refused (`406`).\n* Money and rates are JSON numbers (`2450.75`, `24.99`). Dates are `YYYY-MM-DD`; months are `YYYY-MM`.\n* `PATCH` changes only the fields you send. You can send back exactly what you read: read-only fields are ignored.\n* Some values are **worked out for you** and cannot be set: the billing cycle length (28 to 31 days), its end (closing) date and the closing interval come from `billing_cycle_start_date` and `due_date`; and a listed card supplies the creditor's name, type, issuer and contact details.\n* You can have up to **100 creditors**.\n\n## Errors\n\n| Status | Meaning |\n|---|---|\n| `400` | The body is missing or is not valid JSON. |\n| `403` | Not logged in, the account lacks the *Access Debt Tracker* permission, or a write without a valid `X-CSRF-Token`. |\n| `404` | No such creditor (also what you get for someone else's). |\n| `406` | Not `?_format=json`. |\n| `422` | The data was rejected and **nothing was saved**. The body's `errors` object maps each field to the problem. |"
    },
    "servers": [
        {
            "url": "https://debtsunset.com"
        }
    ],
    "tags": [
        {
            "name": "Sign in",
            "description": "Only needed for session (cookie) authentication, for accounts that have a password. Scripts and AI tools should use a personal API token instead (see the overview)."
        },
        {
            "name": "Creditors",
            "description": "Your credit cards, charge cards and loans."
        },
        {
            "name": "Payoff plan",
            "description": "The extra dollars per month and the strategy saved for you."
        },
        {
            "name": "Annual windfalls",
            "description": "Recurring yearly lump sums (a bonus, a tax refund), one per calendar month."
        },
        {
            "name": "Cards",
            "description": "Reference list: the card and loan products a creditor can be based on."
        }
    ],
    "paths": {
        "/user/login": {
            "post": {
                "tags": [
                    "Sign in"
                ],
                "summary": "Log in",
                "description": "Starts a session. The response sets the session cookie (keep it and send it with every later request) and returns `csrf_token`, which you must send as the `X-CSRF-Token` header on every `POST`, `PATCH` and `DELETE`.\n\nThe account needs the *Access Debt Tracker* permission. HTTP Basic authentication is **not** accepted by this API.",
                "operationId": "login",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "name",
                                    "pass"
                                ],
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Username."
                                    },
                                    "pass": {
                                        "type": "string",
                                        "format": "password"
                                    }
                                }
                            },
                            "example": {
                                "name": "jane",
                                "pass": "your password"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Logged in. The session cookie is set.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "current_user": {
                                            "type": "object",
                                            "properties": {
                                                "uid": {
                                                    "type": "string"
                                                },
                                                "roles": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "string"
                                                    }
                                                },
                                                "name": {
                                                    "type": "string"
                                                }
                                            }
                                        },
                                        "csrf_token": {
                                            "type": "string",
                                            "description": "Send as the X-CSRF-Token header on POST, PATCH and DELETE."
                                        },
                                        "logout_token": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Missing or unrecognised username or password.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Too many failed attempts, or already logged in.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X POST 'https://debtsunset.com/user/login?_format=json' \\\n  -c cookies.txt \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"name\":\"jane\",\"pass\":\"your password\"}'"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\nr = s.post('https://debtsunset.com/user/login?_format=json', json={'name': 'jane', 'pass': 'your password'})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/user/login?_format=json', {\n  method: 'POST',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\"name\":\"jane\",\"pass\":\"your password\"}),\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/session/token": {
            "get": {
                "tags": [
                    "Sign in"
                ],
                "summary": "Get a fresh CSRF token",
                "description": "Returns the CSRF token for the current session as plain text, if you need it again. It is the same value login returned. (No `_format` parameter: the response is not JSON.)",
                "operationId": "csrfToken",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The token.",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/session/token' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/session/token')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/session/token', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/api/debt-tracker/creditors": {
            "get": {
                "tags": [
                    "Creditors"
                ],
                "summary": "List your creditors",
                "operationId": "listCreditors",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "All your creditors, ordered by name.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "creditors": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Creditor"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/api/debt-tracker/creditors?_format=json' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/api/debt-tracker/creditors?_format=json')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/creditors?_format=json', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            },
            "post": {
                "tags": [
                    "Creditors"
                ],
                "summary": "Create a creditor",
                "operationId": "createCreditor",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": [],
                        "csrfToken": []
                    }
                ],
                "description": "Pick the card with `card_tid` (see **Cards**): the card then supplies the creditor's name, credit type, issuer and contact details. Use `card_tid: null` for a creditor that is not in the list, and then also send `creditor_name` and `creditor_type` (and optionally the contact fields).\n\nThe billing cycle is worked out for you from `billing_cycle_start_date` and `due_date`. You may have up to 100 creditors.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreditorCreate"
                            },
                            "examples": {
                                "A listed card": {
                                    "value": {
                                        "card_tid": 215,
                                        "balance_due": 2450.75,
                                        "apr": 24.99,
                                        "due_date": "2026-10-27",
                                        "autopay": true
                                    }
                                },
                                "A creditor that is not in the list": {
                                    "value": {
                                        "card_tid": null,
                                        "creditor_name": "Credit union auto loan",
                                        "creditor_type": "auto_loan",
                                        "balance_due": 9800,
                                        "apr": 6.5,
                                        "billing_cycle_start_date": "2026-09-01",
                                        "due_date": "2026-10-24",
                                        "loan_fixed_payment": 320,
                                        "loan_term_months": 36
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created. The `Location` header is the new creditor's URL.",
                        "headers": {
                            "Location": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Creditor"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "409": {
                        "description": "You already have the maximum number of creditors.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/Invalid"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X POST 'https://debtsunset.com/api/debt-tracker/creditors?_format=json' \\\n  -b cookies.txt \\\n  -H \"X-CSRF-Token: $CSRF_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"card_tid\":215,\"balance_due\":2450.75,\"apr\":24.99,\"billing_cycle_start_date\":\"2026-09-05\",\"due_date\":\"2026-10-27\",\"autopay\":true}'"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.post('https://debtsunset.com/api/debt-tracker/creditors?_format=json', json={'card_tid': 215, 'balance_due': 2450.75, 'apr': 24.99, 'billing_cycle_start_date': '2026-09-05', 'due_date': '2026-10-27', 'autopay': True}, headers={'X-CSRF-Token': token})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/creditors?_format=json', {\n  method: 'POST',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },\n  body: JSON.stringify({\"card_tid\":215,\"balance_due\":2450.75,\"apr\":24.99,\"billing_cycle_start_date\":\"2026-09-05\",\"due_date\":\"2026-10-27\",\"autopay\":true}),\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/api/debt-tracker/creditors/{id}": {
            "parameters": [
                {
                    "name": "id",
                    "in": "path",
                    "required": true,
                    "description": "The creditor's id. Someone else's creditor answers 404, exactly like one that does not exist.",
                    "schema": {
                        "type": "integer",
                        "example": 42
                    }
                }
            ],
            "get": {
                "tags": [
                    "Creditors"
                ],
                "summary": "Read one creditor",
                "operationId": "getCreditor",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The creditor.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Creditor"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/api/debt-tracker/creditors/42?_format=json' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/api/debt-tracker/creditors/42?_format=json')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/creditors/42?_format=json', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            },
            "patch": {
                "tags": [
                    "Creditors"
                ],
                "summary": "Update a creditor",
                "operationId": "updateCreditor",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": [],
                        "csrfToken": []
                    }
                ],
                "description": "Send only the fields you want to change; everything else keeps its value. You can also send back exactly what you read: the read-only fields are ignored.\n\nChanging `billing_cycle_start_date` or `due_date` recalculates the cycle length, end date and closing interval.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreditorUpdate"
                            },
                            "example": {
                                "balance_due": 2100.5,
                                "autopay": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated creditor.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Creditor"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/Invalid"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X PATCH 'https://debtsunset.com/api/debt-tracker/creditors/42?_format=json' \\\n  -b cookies.txt \\\n  -H \"X-CSRF-Token: $CSRF_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"balance_due\":2100.5,\"autopay\":false}'"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.patch('https://debtsunset.com/api/debt-tracker/creditors/42?_format=json', json={'balance_due': 2100.5, 'autopay': False}, headers={'X-CSRF-Token': token})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/creditors/42?_format=json', {\n  method: 'PATCH',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },\n  body: JSON.stringify({\"balance_due\":2100.5,\"autopay\":false}),\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            },
            "delete": {
                "tags": [
                    "Creditors"
                ],
                "summary": "Delete a creditor",
                "operationId": "deleteCreditor",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": [],
                        "csrfToken": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted."
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X DELETE 'https://debtsunset.com/api/debt-tracker/creditors/42?_format=json' \\\n  -b cookies.txt \\\n  -H \"X-CSRF-Token: $CSRF_TOKEN\""
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.delete('https://debtsunset.com/api/debt-tracker/creditors/42?_format=json', headers={'X-CSRF-Token': token})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/creditors/42?_format=json', {\n  method: 'DELETE',\n  credentials: 'include',\n  headers: { 'X-CSRF-Token': csrfToken },\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/api/debt-tracker/plan": {
            "get": {
                "tags": [
                    "Payoff plan"
                ],
                "summary": "Read your saved plan",
                "operationId": "getPlan",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Your plan.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Plan"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/api/debt-tracker/plan?_format=json' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/api/debt-tracker/plan?_format=json')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/plan?_format=json', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            },
            "patch": {
                "tags": [
                    "Payoff plan"
                ],
                "summary": "Update your saved plan",
                "operationId": "updatePlan",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": [],
                        "csrfToken": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "description": "Send `extra_amount`, `strategy` or both; a field you leave out keeps its value. Saving the plan is all it takes: every creditor's extra payment (`min_payment_addition`), the Creditors page, statements and Reports are worked out from it and follow it immediately. (With the `min_only` strategy the Strategy page shows the extra as 0, whatever is stored.)",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PlanUpdate"
                            },
                            "example": {
                                "extra_amount": 250,
                                "strategy": "snowball"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Your plan after the update.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Plan"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/Invalid"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X PATCH 'https://debtsunset.com/api/debt-tracker/plan?_format=json' \\\n  -b cookies.txt \\\n  -H \"X-CSRF-Token: $CSRF_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"extra_amount\":250,\"strategy\":\"snowball\"}'"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.patch('https://debtsunset.com/api/debt-tracker/plan?_format=json', json={'extra_amount': 250, 'strategy': 'snowball'}, headers={'X-CSRF-Token': token})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/plan?_format=json', {\n  method: 'PATCH',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },\n  body: JSON.stringify({\"extra_amount\":250,\"strategy\":\"snowball\"}),\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/api/debt-tracker/windfalls": {
            "get": {
                "tags": [
                    "Annual windfalls"
                ],
                "summary": "Read your annual windfalls",
                "operationId": "getWindfalls",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Your windfalls, ordered by month.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Windfalls"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/api/debt-tracker/windfalls?_format=json' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/api/debt-tracker/windfalls?_format=json')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/windfalls?_format=json', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            },
            "patch": {
                "tags": [
                    "Annual windfalls"
                ],
                "summary": "Replace your annual windfalls",
                "operationId": "setWindfalls",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": [],
                        "csrfToken": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "description": "**Replaces the whole set**: send every windfall you want to keep. An empty list removes them all. One per month, at most 12.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Windfalls"
                            },
                            "example": {
                                "windfalls": [
                                    {
                                        "month": 4,
                                        "amount": 7000
                                    },
                                    {
                                        "month": 12,
                                        "amount": 1500
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Your windfalls after the update.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Windfalls"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/Invalid"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X PATCH 'https://debtsunset.com/api/debt-tracker/windfalls?_format=json' \\\n  -b cookies.txt \\\n  -H \"X-CSRF-Token: $CSRF_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"windfalls\":[{\"month\":4,\"amount\":7000},{\"month\":12,\"amount\":1500}]}'"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.patch('https://debtsunset.com/api/debt-tracker/windfalls?_format=json', json={'windfalls': [{'month': 4, 'amount': 7000}, {'month': 12, 'amount': 1500}]}, headers={'X-CSRF-Token': token})\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/windfalls?_format=json', {\n  method: 'PATCH',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },\n  body: JSON.stringify({\"windfalls\":[{\"month\":4,\"amount\":7000},{\"month\":12,\"amount\":1500}]}),\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        },
        "/api/debt-tracker/cards": {
            "get": {
                "tags": [
                    "Cards"
                ],
                "summary": "List the cards and loan products",
                "operationId": "listCards",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "cookieAuth": []
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/Format"
                    }
                ],
                "description": "The valid values for a creditor's `card_tid`, and the credit types used when a creditor is not in the list.",
                "responses": {
                    "200": {
                        "description": "The reference list.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Cards"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "curl",
                        "label": "curl",
                        "source": "curl -sS -X GET 'https://debtsunset.com/api/debt-tracker/cards?_format=json' \\\n  -b cookies.txt"
                    },
                    {
                        "lang": "Python",
                        "label": "Python (requests)",
                        "source": "import requests\n\ns = requests.Session()   # keeps the session cookie\n# after logging in:  token = s.post(...login...).json()['csrf_token']\nr = s.get('https://debtsunset.com/api/debt-tracker/cards?_format=json')\nprint(r.status_code, r.text)"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "JavaScript (fetch)",
                        "source": "// Same-origin, from a page where the user is logged in.\nconst r = await fetch('/api/debt-tracker/cards?_format=json', {\n  method: 'GET',\n  credentials: 'include',\n});\nconsole.log(r.status, await r.text());"
                    }
                ]
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "dst_ + 40 hex characters",
                "description": "A personal API token, sent as `Authorization: Bearer dst_…`. Create and revoke tokens at `/account/api-tokens` (Lifetime). No CSRF header is needed. Recommended for scripts and AI tools. **Import tokens** (any member, trial included, from `/debt-tracker/ai-import`) last 24 hours and can only `GET /cards`, `GET /creditors`, `GET /creditors/{id}` and `POST /creditors`: made for letting an AI assistant enter your debts from your statements."
            },
            "cookieAuth": {
                "type": "apiKey",
                "in": "cookie",
                "name": "SESS…",
                "description": "The Drupal session cookie set by `POST /user/login` (its name starts with `SESS` or `SSESS`), for accounts that have a password. Your HTTP client keeps it for you (`curl -c/-b`, `requests.Session`, a browser)."
            },
            "csrfToken": {
                "type": "apiKey",
                "in": "header",
                "name": "X-CSRF-Token",
                "description": "Required, together with the session cookie, on `POST`, `PATCH` and `DELETE`. Use the `csrf_token` returned by login (or `GET /session/token`). It stops another website from making changes with your cookies."
            }
        },
        "parameters": {
            "Format": {
                "name": "_format",
                "in": "query",
                "required": true,
                "description": "Always `json`.",
                "schema": {
                    "type": "string",
                    "enum": [
                        "json"
                    ]
                }
            }
        },
        "responses": {
            "Forbidden": {
                "description": "No valid token or session, or the account does not have Lifetime access, or a session write without a valid `X-CSRF-Token`.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Message"
                        }
                    }
                }
            },
            "NotFound": {
                "description": "No such creditor (or it belongs to someone else).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Message"
                        }
                    }
                }
            },
            "Invalid": {
                "description": "The data was rejected. Nothing was saved. `errors` maps each field to what is wrong.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ValidationError"
                        }
                    }
                }
            }
        },
        "schemas": {
            "Message": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string"
                    }
                }
            },
            "ValidationError": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string",
                        "example": "Validation failed."
                    },
                    "errors": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string"
                        },
                        "example": {
                            "due_date": "The due date must fall 20 to 28 days after the cycle end (closing) date. A cycle starting 2026-09-05 lasts 30 days and ends on 2026-10-04, so this due date is 30 days after it."
                        }
                    }
                }
            },
            "Creditor": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 42,
                        "readOnly": true
                    },
                    "card": {
                        "type": "string",
                        "nullable": true,
                        "example": "Autograph",
                        "description": "The card's label.",
                        "readOnly": true
                    },
                    "issuer": {
                        "type": "string",
                        "nullable": true,
                        "example": "Wells Fargo",
                        "description": "The bank or lender.",
                        "readOnly": true
                    },
                    "card_tid": {
                        "type": "integer",
                        "nullable": true,
                        "description": "The card or loan product (an id from `GET /api/debt-tracker/cards`). The card supplies `creditor_name`, `creditor_type`, the issuer and the contact details. `null` means the creditor is not in the list: then `creditor_name` and `creditor_type` are required.",
                        "example": 215
                    },
                    "creditor_name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card.",
                        "example": "Wells Fargo Autograph"
                    },
                    "creditor_type": {
                        "type": "string",
                        "enum": [
                            "credit_card",
                            "charge_card",
                            "personal_loan",
                            "auto_loan"
                        ],
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card.",
                        "example": "credit_card"
                    },
                    "balance_due": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "example": 2450.75
                    },
                    "apr": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Rounded to 3 decimal places.",
                        "example": 24.99
                    },
                    "promo_apr_months": {
                        "type": "integer",
                        "description": "Months left of a 0% promotional APR: `0` (none) or 1 to 21.",
                        "default": 0
                    },
                    "billing_cycle_start_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-09-05",
                        "description": "Optional. First day of the current statement period. Leave it out and it is worked out from `due_date` (the statement is assumed to close 25 days before it). The cycle length (28 to 31 days) and end (closing) date follow from it."
                    },
                    "due_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-10-27",
                        "description": "Payment due date. It must fall 14 to 35 days after the cycle's end (closing) date, otherwise the request is rejected with a 422 that says what the cycle is."
                    },
                    "annual_fee": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "line_of_credit": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "autopay": {
                        "type": "boolean",
                        "default": false
                    },
                    "autopay_method": {
                        "type": "string",
                        "enum": [
                            "pull",
                            "push"
                        ],
                        "default": "pull",
                        "description": "How the payment reaches the creditor when `autopay` (scheduled pay) is on: `push` = bank-pushed bill pay (online bill pay from your bank); `pull` = creditor-pulled autopay (ACH debit, same day). With a payment day set, every scheduled payment leaves the bank on that day."
                    },
                    "processing_days": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5,
                        "default": 0,
                        "description": "Business days a bank-pushed payment takes for the creditor to process (0 = same day). Ignored for creditor-pulled. A bill whose processing would finish after its due date is paid from the previous payment day."
                    },
                    "statement_min_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 206
                    },
                    "payment_plan_amount": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 253
                    },
                    "account_last4": {
                        "type": "string",
                        "pattern": "^(\\d{4})?$",
                        "default": "",
                        "example": "1006",
                        "description": "Optional: the last 4 digits of the account number, shown as \"(…1006)\" beside the payee on This month and the recurring payment page. `\"\"` for none."
                    },
                    "loan_term_months": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 480,
                        "default": 0,
                        "description": "Loans only: months remaining, used to work out a level payment. 0 = not set."
                    },
                    "loan_fixed_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 99999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "unplanned_usage_entries": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "Expected new charges: `{month: \"YYYY-MM\", amount}` items added to the balance in that statement month.",
                        "example": []
                    },
                    "special_additions": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "One-time extra payments: `{month: \"YYYY-MM\", amount}` items applied to this creditor in that month.",
                        "example": [
                            {
                                "month": "2027-03",
                                "amount": 300
                            }
                        ]
                    },
                    "creditor_url": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "uri",
                        "example": "https://creditcards.wellsfargo.com/autograph-visa-credit-card/"
                    },
                    "creditor_phone_1": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "example": "1-800-642-4720"
                    },
                    "creditor_phone_2": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "creditor_contact_email": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "email",
                        "example": ""
                    },
                    "payment_address": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "example": ""
                    },
                    "notes": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "example": ""
                    },
                    "balance_entered": {
                        "type": "number",
                        "example": 2450.75,
                        "description": "The balance as it was last entered. `balance_due` moves on from it by itself (see `balance_estimated`).",
                        "readOnly": true
                    },
                    "balance_as_of": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "example": "2026-09-27",
                        "description": "When `balance_entered` was entered. Saving a creditor or a new `balance_due` resets it to today.",
                        "readOnly": true
                    },
                    "balance_estimated": {
                        "type": "boolean",
                        "example": false,
                        "description": "TRUE when `balance_due` is an estimate: for each payment due date since `balance_as_of`, the saved plan is assumed to have been paid. There is no need to send new balances every month; send one only to correct drift.",
                        "readOnly": true
                    },
                    "min_payment_addition": {
                        "type": "number",
                        "example": 0,
                        "description": "The extra payment your saved plan gives this creditor this month, beyond its minimum. It is worked out from the plan and your current balances every time, so it is always current. To change it, change the plan (`PATCH /api/debt-tracker/plan`); it cannot be set here.",
                        "readOnly": true
                    },
                    "billing_cycle_in_days": {
                        "type": "integer",
                        "example": 30,
                        "description": "Worked out from `billing_cycle_start_date` (28 to 31).",
                        "readOnly": true
                    },
                    "billing_cycle_end_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-10-04",
                        "description": "The last day of the cycle: the statement closing date.",
                        "readOnly": true
                    },
                    "closing_interval_days": {
                        "type": "integer",
                        "example": 23,
                        "description": "Days from the closing date to the due date.",
                        "readOnly": true
                    },
                    "calculated": {
                        "type": "object",
                        "description": "What the Creditors page shows for this creditor.",
                        "properties": {
                            "min_payment": {
                                "type": "number",
                                "example": 56.5
                            },
                            "monthly_payment": {
                                "type": "number",
                                "example": 56.5,
                                "description": "Minimum plus this creditor's extra payment."
                            },
                            "closing_date": {
                                "type": "string",
                                "format": "date",
                                "nullable": true,
                                "example": "2026-10-04",
                                "description": "The next statement closing date."
                            }
                        },
                        "readOnly": true
                    },
                    "changed": {
                        "type": "integer",
                        "example": 1789938221,
                        "description": "Unix time of the last change.",
                        "readOnly": true
                    }
                }
            },
            "CreditorCreate": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "card_tid": {
                        "type": "integer",
                        "nullable": true,
                        "description": "The card or loan product (an id from `GET /api/debt-tracker/cards`). The card supplies `creditor_name`, `creditor_type`, the issuer and the contact details. `null` means the creditor is not in the list: then `creditor_name` and `creditor_type` are required."
                    },
                    "creditor_name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card."
                    },
                    "creditor_type": {
                        "type": "string",
                        "enum": [
                            "credit_card",
                            "charge_card",
                            "personal_loan",
                            "auto_loan"
                        ],
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card."
                    },
                    "balance_due": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "example": 2450.75
                    },
                    "apr": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Rounded to 3 decimal places.",
                        "example": 24.99
                    },
                    "promo_apr_months": {
                        "type": "integer",
                        "description": "Months left of a 0% promotional APR: `0` (none) or 1 to 21.",
                        "default": 0
                    },
                    "billing_cycle_start_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-09-05",
                        "description": "Optional. First day of the current statement period. Leave it out and it is worked out from `due_date` (the statement is assumed to close 25 days before it). The cycle length (28 to 31 days) and end (closing) date follow from it."
                    },
                    "due_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-10-27",
                        "description": "Payment due date. It must fall 14 to 35 days after the cycle's end (closing) date, otherwise the request is rejected with a 422 that says what the cycle is."
                    },
                    "annual_fee": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "line_of_credit": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "autopay": {
                        "type": "boolean",
                        "default": false
                    },
                    "autopay_method": {
                        "type": "string",
                        "enum": [
                            "pull",
                            "push"
                        ],
                        "default": "pull",
                        "description": "How the payment reaches the creditor when `autopay` (scheduled pay) is on: `push` = bank-pushed bill pay (online bill pay from your bank); `pull` = creditor-pulled autopay (ACH debit, same day). With a payment day set, every scheduled payment leaves the bank on that day."
                    },
                    "processing_days": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5,
                        "default": 0,
                        "description": "Business days a bank-pushed payment takes for the creditor to process (0 = same day). Ignored for creditor-pulled. A bill whose processing would finish after its due date is paid from the previous payment day."
                    },
                    "statement_min_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 206
                    },
                    "payment_plan_amount": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 253
                    },
                    "account_last4": {
                        "type": "string",
                        "pattern": "^(\\d{4})?$",
                        "default": "",
                        "example": "1006",
                        "description": "Optional: the last 4 digits of the account number, shown as \"(…1006)\" beside the payee on This month and the recurring payment page. `\"\"` for none."
                    },
                    "loan_term_months": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 480,
                        "default": 0,
                        "description": "Loans only: months remaining, used to work out a level payment. 0 = not set."
                    },
                    "loan_fixed_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 99999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "unplanned_usage_entries": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "Expected new charges: `{month: \"YYYY-MM\", amount}` items added to the balance in that statement month."
                    },
                    "special_additions": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "One-time extra payments: `{month: \"YYYY-MM\", amount}` items applied to this creditor in that month."
                    },
                    "creditor_url": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "uri"
                    },
                    "creditor_phone_1": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "creditor_phone_2": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "creditor_contact_email": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "email"
                    },
                    "payment_address": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "notes": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    }
                },
                "required": [
                    "card_tid",
                    "balance_due",
                    "apr",
                    "due_date"
                ]
            },
            "CreditorUpdate": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "card_tid": {
                        "type": "integer",
                        "nullable": true,
                        "description": "The card or loan product (an id from `GET /api/debt-tracker/cards`). The card supplies `creditor_name`, `creditor_type`, the issuer and the contact details. `null` means the creditor is not in the list: then `creditor_name` and `creditor_type` are required."
                    },
                    "creditor_name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card."
                    },
                    "creditor_type": {
                        "type": "string",
                        "enum": [
                            "credit_card",
                            "charge_card",
                            "personal_loan",
                            "auto_loan"
                        ],
                        "description": "Only used when `card_tid` is null; otherwise it comes from the card."
                    },
                    "balance_due": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "example": 2450.75
                    },
                    "apr": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Rounded to 3 decimal places.",
                        "example": 24.99
                    },
                    "promo_apr_months": {
                        "type": "integer",
                        "description": "Months left of a 0% promotional APR: `0` (none) or 1 to 21.",
                        "default": 0
                    },
                    "billing_cycle_start_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-09-05",
                        "description": "Optional. First day of the current statement period. Leave it out and it is worked out from `due_date` (the statement is assumed to close 25 days before it). The cycle length (28 to 31 days) and end (closing) date follow from it."
                    },
                    "due_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-10-27",
                        "description": "Payment due date. It must fall 14 to 35 days after the cycle's end (closing) date, otherwise the request is rejected with a 422 that says what the cycle is."
                    },
                    "annual_fee": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "line_of_credit": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "autopay": {
                        "type": "boolean",
                        "default": false
                    },
                    "autopay_method": {
                        "type": "string",
                        "enum": [
                            "pull",
                            "push"
                        ],
                        "default": "pull",
                        "description": "How the payment reaches the creditor when `autopay` (scheduled pay) is on: `push` = bank-pushed bill pay (online bill pay from your bank); `pull` = creditor-pulled autopay (ACH debit, same day). With a payment day set, every scheduled payment leaves the bank on that day."
                    },
                    "processing_days": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5,
                        "default": 0,
                        "description": "Business days a bank-pushed payment takes for the creditor to process (0 = same day). Ignored for creditor-pulled. A bill whose processing would finish after its due date is paid from the previous payment day."
                    },
                    "statement_min_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 206
                    },
                    "payment_plan_amount": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0,
                        "example": 253
                    },
                    "account_last4": {
                        "type": "string",
                        "pattern": "^(\\d{4})?$",
                        "default": "",
                        "example": "1006",
                        "description": "Optional: the last 4 digits of the account number, shown as \"(…1006)\" beside the payee on This month and the recurring payment page. `\"\"` for none."
                    },
                    "loan_term_months": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 480,
                        "default": 0,
                        "description": "Loans only: months remaining, used to work out a level payment. 0 = not set."
                    },
                    "loan_fixed_payment": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 99999999.99,
                        "description": "Rounded to 2 decimal places.",
                        "default": 0
                    },
                    "unplanned_usage_entries": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "Expected new charges: `{month: \"YYYY-MM\", amount}` items added to the balance in that statement month."
                    },
                    "special_additions": {
                        "type": "array",
                        "maxItems": 60,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "string",
                                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                                    "example": "2027-03"
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01
                                }
                            }
                        },
                        "description": "One-time extra payments: `{month: \"YYYY-MM\", amount}` items applied to this creditor in that month."
                    },
                    "creditor_url": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "uri"
                    },
                    "creditor_phone_1": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "creditor_phone_2": {
                        "type": "string",
                        "maxLength": 20,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "creditor_contact_email": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details.",
                        "format": "email"
                    },
                    "payment_address": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    },
                    "notes": {
                        "type": "string",
                        "maxLength": 5000,
                        "description": "Only stored when `card_tid` is null; a listed card always supplies its own contact details."
                    }
                },
                "description": "Any of the fields; the read-only ones from the creditor object are accepted and ignored."
            },
            "Plan": {
                "type": "object",
                "properties": {
                    "extra_amount": {
                        "type": "number",
                        "example": 250,
                        "description": "Extra dollars per month distributed by the snowball / avalanche strategy."
                    },
                    "strategy": {
                        "type": "string",
                        "enum": [
                            "min_only",
                            "snowball",
                            "avalanche",
                            "utilization_beatdown"
                        ],
                        "example": "snowball"
                    },
                    "strategies": {
                        "type": "object",
                        "readOnly": true,
                        "additionalProperties": {
                            "type": "string"
                        },
                        "description": "The strategy keys and their names."
                    }
                }
            },
            "PlanUpdate": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "extra_amount": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 99999999.99
                    },
                    "strategy": {
                        "type": "string",
                        "enum": [
                            "min_only",
                            "snowball",
                            "avalanche",
                            "utilization_beatdown"
                        ],
                        "description": "`min_only`: Minimal payments (minimums only, in good standing); `snowball`: Debt snowball (smallest balance first); `avalanche`: Debt avalanche (highest APR first); `utilization_beatdown`: Utilization beatdown (highest-utilization card first)"
                    }
                }
            },
            "Windfalls": {
                "type": "object",
                "required": [
                    "windfalls"
                ],
                "properties": {
                    "windfalls": {
                        "type": "array",
                        "maxItems": 12,
                        "items": {
                            "type": "object",
                            "required": [
                                "month",
                                "amount"
                            ],
                            "properties": {
                                "month": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "maximum": 12,
                                    "description": "1 = January … 12 = December. Recurs every year. Each month at most once."
                                },
                                "amount": {
                                    "type": "number",
                                    "minimum": 0.01,
                                    "maximum": 99999999.99
                                }
                            }
                        }
                    }
                }
            },
            "Cards": {
                "type": "object",
                "properties": {
                    "cards": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "card_tid": {
                                    "type": "integer",
                                    "description": "Use this as `card_tid` when creating a creditor."
                                },
                                "name": {
                                    "type": "string",
                                    "description": "What the creditor will be called."
                                },
                                "label": {
                                    "type": "string"
                                },
                                "issuer": {
                                    "type": "string"
                                },
                                "creditor_type": {
                                    "type": "string",
                                    "enum": [
                                        "credit_card",
                                        "charge_card",
                                        "personal_loan",
                                        "auto_loan"
                                    ]
                                }
                            }
                        }
                    },
                    "creditor_types": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string"
                        },
                        "description": "The credit types and their labels."
                    }
                }
            }
        }
    }
}