{"openapi":"3.1.0","info":{"title":"ToolForte API","version":"1.1.0","description":"Deterministic utility endpoints for developers and AI agents: IBAN validation, VAT calculation, Dutch holidays and working days, cron parsing, regex testing, JSON/CSV/Base64 transforms, diffs, UUIDs, and Dutch test data. All endpoints accept POST with a JSON body and require a free API key. An MCP server with these tools plus file render tools (PDF, screenshots, QR) is available at https://toolforte.com/mcp.","termsOfService":"https://toolforte.com/terms","contact":{"url":"https://toolforte.com/contact"}},"servers":[{"url":"https://toolforte.com"}],"externalDocs":{"description":"API documentation and free API keys","url":"https://toolforte.com/developers"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (tf_...). Create one at https://toolforte.com/developers. It carries a monthly balance of credits shared by the REST API, the MCP server and the AI tools: 1 credit per call, 10 per AI generation, 25 per render. ToolForte Pro raises the monthly balance, and extra credits can be bought at any time without a subscription."}}},"tags":[{"name":"tools","description":"Deterministic utility tools"},{"name":"render","description":"Headless Chrome renders returning hosted files"},{"name":"memory","description":"Persistent state that survives between agent sessions"},{"name":"workflows","description":"Saved chains of tool calls, run in one request"}],"paths":{"/api/v1/tools/text-diff":{"post":{"operationId":"text_diff","summary":"Text Diff","description":"Compare two texts and return a structured diff with additions, deletions, and unchanged lines.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"original":{"type":"string"},"modified":{"type":"string"}}},"example":{"original":"Hello world","modified":"Hello there, world!"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"changes":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"changes":[{"type":"equal","value":"Hello "},{"type":"delete","value":"world"},{"type":"insert","value":"there, world!"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/json-formatter":{"post":{"operationId":"json_formatter","summary":"JSON Formatter","description":"Format, validate, and prettify JSON strings. Returns formatted output or validation errors.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"json":{"type":"string"},"indent":{"type":"integer"}}},"example":{"json":"{\"name\":\"ToolForte\",\"version\":1}","indent":2}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"formatted":{"type":"string"},"valid":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"formatted":"{\n  \"name\": \"ToolForte\",\n  \"version\": 1\n}","valid":true}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/markdown-to-html":{"post":{"operationId":"markdown_to_html","summary":"Markdown to HTML","description":"Convert Markdown content to sanitized HTML. Supports GFM tables, code blocks, and task lists.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"markdown":{"type":"string"}}},"example":{"markdown":"# Hello\n\nThis is **bold** text."}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"html":{"type":"string"}}}}},"example":{"ok":true,"data":{"html":"<h1>Hello</h1>\n<p>This is <strong>bold</strong> text.</p>"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/csv-to-json":{"post":{"operationId":"csv_to_json","summary":"CSV to JSON","description":"Parse CSV data into a JSON array of objects. Auto-detects delimiters and handles quoted fields.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"},"delimiter":{"type":"string"}}},"example":{"csv":"name,age\nAlice,30\nBob,25","delimiter":","}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"string"}}}},"rows":{"type":"integer"},"columns":{"type":"integer"}}}}},"example":{"ok":true,"data":{"data":[{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}],"rows":2,"columns":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/url-encoder":{"post":{"operationId":"url_encoder","summary":"URL Encoder/Decoder","description":"Encode or decode URL components. Handles special characters and unicode.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"hello world & foo=bar","mode":"encode"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"hello%20world%20%26%20foo%3Dbar"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/base64":{"post":{"operationId":"base64","summary":"Base64 Encode/Decode","description":"Encode text to Base64 or decode Base64 strings back to plain text.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"Hello, ToolForte!","mode":"encode"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"SGVsbG8sIFRvb2xGb3J0ZSE="}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/regex-tester":{"post":{"operationId":"regex_tester","summary":"Regex Tester","description":"Test a regular expression against a string. Returns all matches with groups and indices.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pattern":{"type":"string"},"flags":{"type":"string"},"text":{"type":"string"}}},"example":{"pattern":"(\\d{4})-(\\d{2})-(\\d{2})","flags":"g","text":"Today is 2026-04-08 and tomorrow is 2026-04-09."}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"match":{"type":"string"},"index":{"type":"integer"},"groups":{"type":"array","items":{"type":"string"}}}}},"count":{"type":"integer"}}}}},"example":{"ok":true,"data":{"matches":[{"match":"2026-04-08","index":9,"groups":["2026","04","08"]},{"match":"2026-04-09","index":37,"groups":["2026","04","09"]}],"count":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/color-converter":{"post":{"operationId":"color_converter","summary":"Color Converter","description":"Convert colors between HEX, RGB, HSL, and HSB formats. Returns all representations.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"color":{"type":"string"},"format":{"type":"string"}}},"example":{"color":"#4338ca","format":"hex"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"hex":{"type":"string"},"rgb":{"type":"object","properties":{"r":{"type":"integer"},"g":{"type":"integer"},"b":{"type":"integer"}}},"hsl":{"type":"object","properties":{"h":{"type":"integer"},"s":{"type":"integer"},"l":{"type":"integer"}}},"hsb":{"type":"object","properties":{"h":{"type":"integer"},"s":{"type":"integer"},"b":{"type":"integer"}}}}}}},"example":{"ok":true,"data":{"hex":"#4338ca","rgb":{"r":67,"g":56,"b":202},"hsl":{"h":245,"s":58,"l":51},"hsb":{"h":245,"s":72,"b":79}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/password-strength":{"post":{"operationId":"password_strength","summary":"Password Strength","description":"Analyze password strength and return a score with detailed feedback and suggestions.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string"}}},"example":{"password":"MyS3cur3P@ss!"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"score":{"type":"integer"},"label":{"type":"string"},"crack_time":{"type":"string"},"feedback":{"type":"object","properties":{"suggestions":{"type":"array","items":{}},"warning":{"type":"null"}}}}}}},"example":{"ok":true,"data":{"score":4,"label":"Strong","crack_time":"centuries","feedback":{"suggestions":[],"warning":null}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/word-counter":{"post":{"operationId":"word_counter","summary":"Word Counter","description":"Count words, characters, sentences, and paragraphs. Estimates reading and speaking time.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}}},"example":{"text":"The quick brown fox jumps over the lazy dog."}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"words":{"type":"integer"},"characters":{"type":"integer"},"characters_no_spaces":{"type":"integer"},"sentences":{"type":"integer"},"paragraphs":{"type":"integer"},"reading_time_seconds":{"type":"integer"}}}}},"example":{"ok":true,"data":{"words":9,"characters":44,"characters_no_spaces":36,"sentences":1,"paragraphs":1,"reading_time_seconds":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/cron-parser":{"post":{"operationId":"cron_parser","summary":"Cron Parser","description":"Parse a cron expression into a human-readable description and list the next N scheduled runs.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expression":{"type":"string"},"count":{"type":"integer"}}},"example":{"expression":"0 9 * * MON-FRI","count":3}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"description":{"type":"string"},"next_runs":{"type":"array","items":{"type":"string"}},"valid":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"description":"At 09:00 on every day-of-week from Monday through Friday","next_runs":["2026-04-09T09:00:00Z","2026-04-10T09:00:00Z","2026-04-11T09:00:00Z"],"valid":true}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/uuid-generator":{"post":{"operationId":"uuid_generator","summary":"UUID Generator","description":"Generate one or more UUIDs (v4). Optionally return as uppercase or without dashes.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"uppercase":{"type":"boolean"}}},"example":{"count":2,"uppercase":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"uuids":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"uuids":["f47ac10b-58cc-4372-a567-0e02b2c3d479","7c9e6679-7425-40de-944b-e07fc1f90ae7"]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/iban-validator":{"post":{"operationId":"iban_validator","summary":"IBAN Validator","description":"Validate an IBAN number. Returns country, bank code, and checksum verification.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"iban":{"type":"string"}}},"example":{"iban":"DE89370400440532013000"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"country":{"type":"string"},"country_code":{"type":"string"},"bank_code":{"type":"string"},"checksum":{"type":"string"}}}}},"example":{"ok":true,"data":{"valid":true,"country":"Germany","country_code":"DE","bank_code":"37040044","checksum":"89"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/slug-generator":{"post":{"operationId":"slug_generator","summary":"Slug Generator","description":"Convert any text into a URL-friendly slug. Handles unicode, diacritics, and special characters.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"separator":{"type":"string"}}},"example":{"text":"Hello World! This is a Test.","separator":"-"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"slug":{"type":"string"}}}}},"example":{"ok":true,"data":{"slug":"hello-world-this-is-a-test"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/email-validator":{"post":{"operationId":"email_validator","summary":"Email Validator","description":"Validate email address syntax and check for common typos in popular domain names.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}},"example":{"email":"user@gmial.com"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"syntax_ok":{"type":"boolean"},"suggestion":{"type":"string"},"disposable":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"valid":true,"syntax_ok":true,"suggestion":"user@gmail.com","disposable":false}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/vat-calculator":{"post":{"operationId":"vat_calculator","summary":"VAT Calculator","description":"Add VAT to a net amount or extract VAT from a gross amount, at any rate. Built for invoicing and pricing flows.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"rate":{"type":"integer"},"mode":{"type":"string"}}},"example":{"amount":100,"rate":21,"mode":"add"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"net":{"type":"integer"},"vat":{"type":"integer"},"gross":{"type":"integer"},"rate":{"type":"integer"}}}}},"example":{"ok":true,"data":{"net":100,"vat":21,"gross":121,"rate":21}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/vat-number-check":{"post":{"operationId":"vat_number_check","summary":"VAT Number Format Check","description":"Validate the format of EU (plus GB/CH) VAT numbers before storing them. Syntax check only, no VIES lookup.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vatNumber":{"type":"string"}}},"example":{"vatNumber":"NL123456789B01"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"string"},"normalized":{"type":"string"},"country":{"type":"string"},"validFormat":{"type":"boolean"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":"NL123456789B01","normalized":"NL123456789B01","country":"NL","validFormat":true,"note":"Format is valid. This is a syntax check only; use the EU VIES service to confirm registration."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-holidays":{"post":{"operationId":"dutch_holidays","summary":"Dutch Public Holidays","description":"All Dutch public holidays for a given year, including movable feasts like Easter, Ascension Day, and King's Day.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"integer"}}},"example":{"year":2026}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"year":{"type":"integer"},"holidays":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"name":{"type":"string"},"nameEn":{"type":"string"},"dayOff":{"type":"boolean"}}}}}}}},"example":{"ok":true,"data":{"year":2026,"holidays":[{"date":"2026-04-27","name":"Koningsdag","nameEn":"King's Day","dayOff":true}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/working-days":{"post":{"operationId":"working_days","summary":"Working Days Calculator","description":"Count working days (Monday to Friday) between two dates, optionally excluding Dutch public holidays. Ideal for SLA and payroll calculations.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"excludeDutchHolidays":{"type":"boolean"}}},"example":{"start":"2026-09-01","end":"2026-09-30","excludeDutchHolidays":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"totalDays":{"type":"integer"},"workingDays":{"type":"integer"},"weekendDays":{"type":"integer"},"holidaysExcluded":{"type":"array","items":{}}}}}},"example":{"ok":true,"data":{"start":"2026-09-01","end":"2026-09-30","totalDays":30,"workingDays":22,"weekendDays":8,"holidaysExcluded":[]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/test-bsn-generator":{"post":{"operationId":"test_bsn_generator","summary":"Test BSN Generator","description":"Generate Dutch BSN test numbers that pass the elfproef (11-check) but are not linked to real persons. For development and test environments.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"}}},"example":{"count":3}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"count":{"type":"integer"},"bsns":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"count":3,"bsns":["111222333","123456782","987654321"],"note":"Test numbers that pass the elfproef (11-check). They are not linked to real persons."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/test-iban-generator":{"post":{"operationId":"test_iban_generator","summary":"Test IBAN Generator","description":"Generate structurally valid test IBANs (correct mod-97 check digits) for NL, DE, BE, FR, or GB that are not linked to real bank accounts. For development and test environments.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string"},"count":{"type":"integer"}}},"example":{"country":"NL","count":3}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"count":{"type":"integer"},"country":{"type":"string"},"ibans":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"count":3,"country":"NL","ibans":["NL91ABNA0417164300","NL02RABO0123456789","NL39INGB0001234567"],"note":"Structurally valid IBANs (correct mod-97 check digits) not linked to real bank accounts. For test environments only."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/brp-test-data-generator":{"post":{"operationId":"brp_test_data_generator","summary":"BRP Test Data Generator","description":"Generate Dutch BRP/GBA test persons: valid BSNs, families, addresses and life events. Every setting on the tool page is a field here, and the same seed always returns the same people, so a test suite can assert on fixed values. Formats: json, csv, gba-totaalfile. Full parameter reference at /developers/generators.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string"},"options":{"type":"object","properties":{"count":{"type":"integer"},"seed":{"type":"integer"},"minAge":{"type":"integer"},"maxAge":{"type":"integer"},"eventMix":{"type":"object","properties":{"married":{"type":"integer"},"widowed":{"type":"integer"},"deceased":{"type":"integer"}}}}}}},"example":{"format":"json","options":{"count":25,"seed":20270101,"minAge":60,"maxAge":80,"eventMix":{"married":5,"widowed":3,"deceased":1}}}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"format":{"type":"string"},"count":{"type":"integer"},"seed":{"type":"integer"},"people":{"type":"array","items":{"type":"object","properties":{"bsn":{"type":"string"},"lastName":{"type":"string"},"dateOfBirth":{"type":"string"},"lifeEvent":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"format":"json","count":25,"seed":20270101,"people":[{"bsn":"123456782","lastName":"de Vries","dateOfBirth":"19620314","lifeEvent":"married"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/upa-file-generator":{"post":{"operationId":"upa_file_generator","summary":"UPA File Generator","description":"Generate UPA pension declaration XML for one or more periods: employments, schemes, household situations and, when you ask for them, deliberate defects that the receiving system should reject. Every setting on the tool page is a field here and the seed makes it repeatable. Formats: xml, summary, participants-csv. Full parameter reference at /developers/generators.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string"},"options":{"type":"object","properties":{"schemeType":{"type":"string"},"population":{"type":"object","properties":{"count":{"type":"integer"},"seed":{"type":"integer"}}},"period":{"type":"object","properties":{"startYear":{"type":"integer"},"startMonth":{"type":"integer"},"months":{"type":"integer"}}},"defects":{"type":"object","properties":{"invalidBsn":{"type":"boolean"}}}}}}},"example":{"format":"xml","options":{"schemeType":"FPR","population":{"count":50,"seed":20270101},"period":{"startYear":2027,"startMonth":1,"months":3},"defects":{"invalidBsn":true}}}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"format":{"type":"string"},"fileCount":{"type":"integer"},"peopleCount":{"type":"integer"},"warnings":{"type":"array","items":{"type":"string"}},"files":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"periodLabel":{"type":"string"},"employments":{"type":"integer"},"content":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"format":"xml","fileCount":3,"peopleCount":50,"warnings":["1 deliberate defect injected. These files are meant to be rejected."],"files":[{"name":"UPA_202701_001.xml","periodLabel":"2027-01","employments":50,"content":"<?xml version=\"1.0\"?>…"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/text-case-converter":{"post":{"operationId":"text_case_converter","summary":"Text Case Converter","description":"Convert a text to one case style: upper, lower, title, sentence, camel, pascal, snake or kebab. Returns the converted string.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"hello world from ToolForte","mode":"snake"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"mode":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"hello_world_from_tool_forte","mode":"snake"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/lorem-ipsum-generator":{"post":{"operationId":"lorem_ipsum_generator","summary":"Lorem Ipsum Generator","description":"Generate classic lorem ipsum placeholder text as paragraphs, sentences, words or a list, optionally wrapped in HTML. Deterministic: the same options give the same text.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"type":{"type":"string"},"startWithLorem":{"type":"boolean"},"html":{"type":"boolean"},"seed":{"type":"integer"}}},"example":{"count":2,"type":"sentences","startWithLorem":true,"html":false,"seed":1}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"text":{"type":"string"},"type":{"type":"string"},"count":{"type":"integer"},"words":{"type":"integer"},"characters":{"type":"integer"},"paragraphs":{"type":"integer"}}}}},"example":{"ok":true,"data":{"text":"Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt. Lorem ipsum dolor sit amet consectetur adipiscing elit.","type":"sentences","count":2,"words":26,"characters":172,"paragraphs":1}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/sort-lines":{"post":{"operationId":"sort_lines","summary":"Sort Lines","description":"Sort the lines of a text A to Z, Z to A, by length, in natural numeric order, or shuffle them with a seed. Can drop duplicates and ignore case. Returns the sorted text and line count.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"},"caseInsensitive":{"type":"boolean"},"removeDuplicates":{"type":"boolean"},"reverse":{"type":"boolean"},"seed":{"type":"integer"}}},"example":{"text":"banana\napple\nCherry\nitem10\nitem2","mode":"natural","caseInsensitive":true,"removeDuplicates":false,"reverse":false,"seed":1}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"mode":{"type":"string"},"lines":{"type":"integer"},"removedDuplicates":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"apple\nbanana\nCherry\nitem2\nitem10","mode":"natural","lines":5,"removedDuplicates":0}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/remove-duplicate-lines":{"post":{"operationId":"remove_duplicate_lines","summary":"Remove Duplicate Lines","description":"Remove repeated lines from a text while keeping the original order. Returns the cleaned text with how many lines were removed.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"caseInsensitive":{"type":"boolean"},"trim":{"type":"boolean"},"keepFirst":{"type":"boolean"}}},"example":{"text":"apple\nbanana\napple\n banana\ncherry","caseInsensitive":false,"trim":true,"keepFirst":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"total":{"type":"integer"},"kept":{"type":"integer"},"removed":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"apple\nbanana\ncherry","total":5,"kept":3,"removed":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/reverse-text":{"post":{"operationId":"reverse_text","summary":"Reverse Text","description":"Reverse a text by characters, by word order per line, or by the characters of each line. Returns the reversed string.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"one two three","mode":"words"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"mode":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"three two one","mode":"words"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/find-and-replace":{"post":{"operationId":"find_and_replace","summary":"Find and Replace","description":"Replace every occurrence of a string or regular expression in a text. Supports case sensitivity, whole word matching and regex groups in the replacement. Returns the new text and the number of replacements.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"find":{"type":"string"},"replace":{"type":"string"},"caseSensitive":{"type":"boolean"},"wholeWord":{"type":"boolean"},"useRegex":{"type":"boolean"}}},"example":{"text":"The cat sat on the mat with another cat.","find":"cat","replace":"dog","caseSensitive":false,"wholeWord":true,"useRegex":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"count":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"The dog sat on the mat with another dog.","count":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/word-frequency-counter":{"post":{"operationId":"word_frequency_counter","summary":"Word Frequency Counter","description":"Count how often each word occurs in a text and return a ranked table, most frequent first, with counts and percentages. Can ignore case, skip short words and drop common stop words.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"ignoreCase":{"type":"boolean"},"minLength":{"type":"integer"},"excludeStopWords":{"type":"boolean"},"limit":{"type":"integer"}}},"example":{"text":"the cat and the dog and the bird","ignoreCase":true,"minLength":1,"excludeStopWords":false,"limit":3}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"words":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"integer"},"word":{"type":"string"},"count":{"type":"integer"},"percent":{"type":"number"}}}},"totalWords":{"type":"integer"},"uniqueWords":{"type":"integer"}}}}},"example":{"ok":true,"data":{"words":[{"rank":1,"word":"the","count":3,"percent":37.5},{"rank":2,"word":"and","count":2,"percent":25},{"rank":3,"word":"bird","count":1,"percent":12.5}],"totalWords":8,"uniqueWords":5}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/reading-time-calculator":{"post":{"operationId":"reading_time_calculator","summary":"Reading Time Calculator","description":"Estimate how long a text takes to read and to speak aloud, from the text itself or a word count, at a chosen words-per-minute plus slow, average and fast presets.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"readingWpm":{"type":"integer"},"speakingWpm":{"type":"integer"}}},"example":{"text":"Knowing how long your content takes to read helps you plan articles, documentation and talks. This short paragraph is here as a sample.","readingWpm":238,"speakingWpm":150}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"wordCount":{"type":"integer"},"characters":{"type":"integer"},"sentences":{"type":"integer"},"reading":{"type":"object","properties":{"label":{"type":"string"},"wpm":{"type":"integer"},"minutes":{"type":"number"},"time":{"type":"string"}}},"speaking":{"type":"object","properties":{"label":{"type":"string"},"wpm":{"type":"integer"},"minutes":{"type":"number"},"time":{"type":"string"}}},"readingPresets":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"wpm":{"type":"integer"},"minutes":{"type":"number"},"time":{"type":"string"}}}},"speakingPresets":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"wpm":{"type":"integer"},"minutes":{"type":"number"},"time":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"wordCount":23,"characters":135,"sentences":2,"reading":{"label":"Custom","wpm":238,"minutes":0.1,"time":"< 1 min"},"speaking":{"label":"Custom","wpm":150,"minutes":0.15,"time":"< 1 min"},"readingPresets":[{"label":"Slow Reader","wpm":150,"minutes":0.15,"time":"< 1 min"}],"speakingPresets":[{"label":"Slow Speech","wpm":100,"minutes":0.23,"time":"< 1 min"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/rot13":{"post":{"operationId":"rot13","summary":"ROT13 Cipher","description":"Apply ROT13, or any Caesar shift from 1 to 25, to a text. Letters rotate and keep their case; digits and punctuation stay as they are. Decoding with shift 13 gives the original back.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"shift":{"type":"integer"},"decode":{"type":"boolean"}}},"example":{"text":"Hello, World!","shift":13,"decode":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"shift":{"type":"integer"},"decode":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"result":"Uryyb, Jbeyq!","shift":13,"decode":false}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/morse-code-translator":{"post":{"operationId":"morse_code_translator","summary":"Morse Code Translator","description":"Translate text to International Morse code or Morse back to text. Letters are separated by a space and words by a slash; characters without a Morse code are dropped.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"SOS help","mode":"encode"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"mode":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"... --- ... / .... . .-.. .--.","mode":"encode"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/numbers-to-words":{"post":{"operationId":"numbers_to_words","summary":"Numbers to Words","description":"Write a number out in English words, up to 999 quadrillion, with decimals read digit by digit or as a currency amount in dollars, euros or pounds. Optional British style with \"and\".","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"number":{"type":"string"},"mode":{"type":"string"},"currency":{"type":"string"},"british":{"type":"boolean"}}},"example":{"number":"1234.56","mode":"plain","currency":"dollars","british":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"words":{"type":"string"},"number":{"type":"string"}}}}},"example":{"ok":true,"data":{"words":"One thousand two hundred thirty-four point five six","number":"1234.56"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/roman-numeral-converter":{"post":{"operationId":"roman_numeral_converter","summary":"Roman Numeral Converter","description":"Convert a whole number from 1 to 3999 into a Roman numeral, or a Roman numeral back into a number, with strict validation and a symbol-by-symbol breakdown. Detects the direction from the input unless a mode is given.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string"},"mode":{"type":"string"}}},"example":{"value":"1994","mode":"auto"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"direction":{"type":"string"},"roman":{"type":"string"},"number":{"type":"integer"},"parts":{"type":"array","items":{"type":"object","properties":{"value":{"type":"integer"},"symbol":{"type":"string"}}}},"breakdown":{"type":"string"}}}}},"example":{"ok":true,"data":{"direction":"toRoman","roman":"MCMXCIV","number":1994,"parts":[{"value":1000,"symbol":"M"}],"breakdown":"M (1000) + CM (900) + XC (90) + IV (4)"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/text-cleaner":{"post":{"operationId":"text_cleaner","summary":"Text Cleaner","description":"Clean up messy text: collapse extra spaces, trim lines, drop empty lines, strip HTML tags, remove punctuation, digits, emojis or control characters, and straighten smart quotes. Returns the cleaned text and how many characters each step removed.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"extraSpaces":{"type":"boolean"},"trimLines":{"type":"boolean"},"emptyLines":{"type":"boolean"},"lineBreaks":{"type":"boolean"},"htmlTags":{"type":"boolean"},"punctuation":{"type":"boolean"},"numbers":{"type":"boolean"},"emojis":{"type":"boolean"},"plainQuotes":{"type":"boolean"},"controlChars":{"type":"boolean"}}},"example":{"text":"<p>Hello   world</p>\n\n\n  “quoted”  text  ","extraSpaces":true,"trimLines":true,"emptyLines":true,"lineBreaks":false,"htmlTags":true,"punctuation":false,"numbers":false,"emojis":false,"plainQuotes":true,"controlChars":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"charactersBefore":{"type":"integer"},"charactersAfter":{"type":"integer"},"charactersRemoved":{"type":"integer"},"steps":{"type":"array","items":{"type":"object","properties":{"option":{"type":"string"},"charactersRemoved":{"type":"integer"}}}},"changed":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"result":"Hello world\n\"quoted\" text","charactersBefore":41,"charactersAfter":25,"charactersRemoved":16,"steps":[{"option":"htmlTags","charactersRemoved":7}],"changed":["htmlTags","plainQuotes","trimLines","emptyLines","extraSpaces"]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/json-to-csv":{"post":{"operationId":"json_to_csv","summary":"JSON to CSV","description":"Turn a JSON array of objects, or a JSON string holding one, into CSV text. Nested objects become dotted column names such as address.city, arrays are written as JSON inside the cell, and every key seen in any row becomes a column.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"json":{"type":"string"},"delimiter":{"type":"string"},"includeHeader":{"type":"boolean"},"flatten":{"type":"boolean"}}},"example":{"json":"[{\"name\":\"Alice\",\"address\":{\"city\":\"Utrecht\"}},{\"name\":\"Bob\",\"address\":{\"city\":\"Leiden\"}}]","delimiter":",","includeHeader":true,"flatten":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"csv":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"rowCount":{"type":"integer"}}}}},"example":{"ok":true,"data":{"csv":"name,address.city\nAlice,Utrecht\nBob,Leiden","columns":["name","address.city"],"rowCount":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/json-to-yaml":{"post":{"operationId":"json_to_yaml","summary":"JSON to YAML","description":"Convert a JSON string to YAML text. Strings are quoted only when a YAML reader would otherwise see a number, boolean, date or empty value, multi-line strings become block scalars, and keys can be sorted.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"json":{"type":"string"},"indent":{"type":"integer"},"sortKeys":{"type":"boolean"},"nullStyle":{"type":"string"}}},"example":{"json":"{\"name\":\"ToolForte\",\"tags\":[\"fast\",\"free\"],\"owner\":null}","indent":2,"sortKeys":false,"nullStyle":"null"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"yaml":{"type":"string"}}}}},"example":{"ok":true,"data":{"yaml":"name: ToolForte\ntags:\n  - fast\n  - free\nowner: null"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/yaml-to-json":{"post":{"operationId":"yaml_to_json","summary":"YAML to JSON","description":"Parse YAML text and return both the value and its JSON text. Reads maps, lists, inline [lists] and {maps}, quoted and block scalars and comments. Anchors, aliases, merge keys, tags and multi-document files are not supported and are reported with the line number.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"yaml":{"type":"string"},"indent":{"type":"integer"}}},"example":{"yaml":"name: ToolForte\ntags:\n  - fast\n  - free\nport: 8080","indent":2}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"value":{"type":"object","properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"port":{"type":"integer"}}},"json":{"type":"string"}}}}},"example":{"ok":true,"data":{"value":{"name":"ToolForte","tags":["fast","free"],"port":8080},"json":"{\n  \"name\": \"ToolForte\",\n  \"tags\": [\n    \"fast\",\n    \"free\"\n  ],\n  \"port\": 8080\n}"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/xml-to-json":{"post":{"operationId":"xml_to_json","summary":"XML to JSON","description":"Parse XML into a JSON object keyed by the root element. Attributes become keys with a prefix (\"@id\"), text beside attributes or children lands under \"#text\", repeated elements become arrays, and namespace prefixes can be dropped. Malformed XML is reported with the line number; DTD-declared entities are not expanded.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"xml":{"type":"string"},"attributePrefix":{"type":"string"},"dropNamespaces":{"type":"boolean"},"indent":{"type":"integer"}}},"example":{"xml":"<order id=\"42\"><item sku=\"A1\">Bolts</item><item sku=\"B2\">Nuts</item></order>","attributePrefix":"@","dropNamespaces":true,"indent":2}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"value":{"type":"object","properties":{"order":{"type":"object","properties":{"@id":{"type":"string"},"item":{"type":"array","items":{"type":"object","properties":{"@sku":{"type":"string"},"#text":{"type":"string"}}}}}}}},"json":{"type":"string"},"elements":{"type":"integer"}}}}},"example":{"ok":true,"data":{"value":{"order":{"@id":"42","item":[{"@sku":"A1","#text":"Bolts"},{"@sku":"B2","#text":"Nuts"}]}},"json":"{\n  \"order\": {\n    \"@id\": \"42\",\n    \"item\": [\n      {\n        \"@sku\": \"A1\",\n        \"#text\": \"Bolts\"\n      },\n      {\n        \"@sku\": \"B2\",\n        \"#text\": \"Nuts\"\n      }\n    ]\n  }\n}","elements":3}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/json-to-xml":{"post":{"operationId":"json_to_xml","summary":"JSON to XML","description":"Serialise a JSON string as XML. Object keys become element names, array items repeat the parent key, null and empty objects become self-closing tags, and the root element name is yours to choose. A top-level array is wrapped in the root with one item element per entry.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"json":{"type":"string"},"rootName":{"type":"string"},"declaration":{"type":"boolean"}}},"example":{"json":"{\"name\":\"ToolForte\",\"tags\":[\"fast\",\"free\"]}","rootName":"product","declaration":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"xml":{"type":"string"}}}}},"example":{"ok":true,"data":{"xml":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<product>\n  <name>ToolForte</name>\n  <tags>fast</tags>\n  <tags>free</tags>\n</product>"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/xml-formatter":{"post":{"operationId":"xml_formatter","summary":"XML Formatter","description":"Pretty-print XML with consistent indentation, or minify it by removing the whitespace between tags. Elements that hold only text stay on one line, and comments, CDATA and processing instructions are kept. Malformed XML is reported with the line number rather than reshaped.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"xml":{"type":"string"},"mode":{"type":"string"},"indent":{"type":"integer"}}},"example":{"xml":"<a><b x=\"1\">text</b><c/></a>","mode":"pretty","indent":2}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"xml":{"type":"string"}}}}},"example":{"ok":true,"data":{"xml":"<a>\n  <b x=\"1\">text</b>\n  <c/>\n</a>"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/csv-to-markdown":{"post":{"operationId":"csv_to_markdown","summary":"CSV to Markdown","description":"Turn CSV text into a GitHub-style Markdown table. Detects the delimiter (comma, semicolon, tab or pipe) unless told, reads quoted fields with embedded delimiters and newlines, and can align columns and pad cells so the table lines up in plain text.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"},"delimiter":{"type":"string"},"firstRowHeader":{"type":"boolean"},"padded":{"type":"boolean"},"alignment":{"type":"string"}}},"example":{"csv":"name,age\nAlice,30\nBob,25","delimiter":"auto","firstRowHeader":true,"padded":true,"alignment":"left"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"markdown":{"type":"string"},"rows":{"type":"integer"},"columns":{"type":"integer"},"delimiter":{"type":"string"},"raggedLines":{"type":"array","items":{}}}}}},"example":{"ok":true,"data":{"markdown":"| name  | age |\n| :---- | :-- |\n| Alice | 30  |\n| Bob   | 25  |","rows":2,"columns":2,"delimiter":",","raggedLines":[]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/sql-formatter":{"post":{"operationId":"sql_formatter","summary":"SQL Formatter","description":"Pretty-print a SQL statement: keywords in upper case, each major clause (SELECT, FROM, WHERE, JOIN, GROUP BY, ORDER BY and so on) on its own line, and indentation inside CASE and parentheses. String literals are left untouched. A formatter, not a parser: it does not check that the SQL is valid.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sql":{"type":"string"}}},"example":{"sql":"select id, name from users where active = 1 and role = 'admin' order by name"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sql":{"type":"string"}}}}},"example":{"ok":true,"data":{"sql":"SELECT id, name\nFROM users\nWHERE active = 1\nAND role = 'admin'\nORDER BY name"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/html-entity-encoder":{"post":{"operationId":"html_entity_encoder","summary":"HTML Entity Encoder","description":"Encode text as HTML entities or decode entities back to text. Encoding always covers &, <, >, quotes and common typographic characters, and can cover every non-ASCII character too, named where HTML has a name (&eacute;) and numeric otherwise. Decoding reads numeric references and HTML 4 named entities; unknown names are left as they are.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"},"encodeNonAscii":{"type":"boolean"}}},"example":{"text":"<a href=\"x\">Tom & Jerry</a>","mode":"encode","encodeNonAscii":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"output":{"type":"string"},"mode":{"type":"string"}}}}},"example":{"ok":true,"data":{"output":"&lt;a href=&quot;x&quot;&gt;Tom &amp; Jerry&lt;/a&gt;","mode":"encode"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/json-to-typescript":{"post":{"operationId":"json_to_typescript","summary":"JSON to TypeScript","description":"Derive TypeScript interfaces from a JSON sample. Nested objects become their own interfaces named after their key, arrays become typed arrays (a union when the items differ), null stays null, and properties can be made optional or readonly.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"json":{"type":"string"},"rootName":{"type":"string"},"exportInterfaces":{"type":"boolean"},"optionalProps":{"type":"boolean"},"readonlyProps":{"type":"boolean"}}},"example":{"json":"{\"id\":1,\"name\":\"Ada\",\"address\":{\"city\":\"Utrecht\"},\"tags\":[\"a\",\"b\"]}","rootName":"User","exportInterfaces":true,"optionalProps":false,"readonlyProps":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"typescript":{"type":"string"},"declarations":{"type":"integer"}}}}},"example":{"ok":true,"data":{"typescript":"export interface Address {\n  city: string;\n}\n\nexport interface User {\n  id: number;\n  name: string;\n  address: Address;\n  tags: string[];\n}","declarations":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/hash-generator":{"post":{"operationId":"hash_generator","summary":"Hash Generator","description":"Hash a text with SHA-1, SHA-256, SHA-384 or SHA-512 and return the hex digest, or all four at once. Text is hashed as UTF-8. No MD5: it is broken and Web Crypto does not offer it.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"algorithm":{"type":"string"}}},"example":{"text":"hello world","algorithm":"SHA-256"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"algorithms":{"type":"array","items":{"type":"string"}},"hashes":{"type":"object","properties":{"SHA-256":{"type":"string"}}},"byteLength":{"type":"integer"}}}}},"example":{"ok":true,"data":{"algorithms":["SHA-256"],"hashes":{"SHA-256":"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"},"byteLength":11}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/jwt-decoder":{"post":{"operationId":"jwt_decoder","summary":"JWT Decoder","description":"Decode a JSON Web Token into its header and payload, and explain the exp, iat and nbf claims as ISO dates with an expired flag. The signature is returned but never verified.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"now":{"type":"string"}}},"example":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c","now":"2026-09-07T12:00:00Z"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"typ":{"type":"string"}}},"payload":{"type":"object","properties":{"sub":{"type":"string"},"name":{"type":"string"},"iat":{"type":"integer"}}},"signature":{"type":"string"},"expiresAt":{"type":"null"},"issuedAt":{"type":"object","properties":{"unix":{"type":"integer"},"iso":{"type":"string"}}},"notBefore":{"type":"null"},"expired":{"type":"null"},"checkedAt":{"type":"string"},"verified":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"header":{"alg":"HS256","typ":"JWT"},"payload":{"sub":"1234567890","name":"John Doe","iat":1516239022},"signature":"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c","expiresAt":null,"issuedAt":{"unix":1516239022,"iso":"2018-01-18T01:30:22.000Z"},"notBefore":null,"expired":null,"checkedAt":"2026-09-07T12:00:00.000Z","verified":false}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/timestamp-converter":{"post":{"operationId":"timestamp_converter","summary":"Timestamp Converter","description":"Convert a Unix timestamp (seconds or milliseconds) to an ISO 8601 UTC date, or an ISO date to a Unix timestamp. Returns both timestamps, the ISO and RFC 7231 forms, and the weekday.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"integer"},"unit":{"type":"string"}}},"example":{"value":1725710400,"unit":"auto"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"integer"},"interpretedAs":{"type":"string"},"unixSeconds":{"type":"integer"},"unixMilliseconds":{"type":"integer"},"iso":{"type":"string"},"utc":{"type":"string"},"date":{"type":"string"},"time":{"type":"string"},"weekday":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":1725710400,"interpretedAs":"seconds","unixSeconds":1725710400,"unixMilliseconds":1725710400000,"iso":"2024-09-07T12:00:00.000Z","utc":"Sat, 07 Sep 2024 12:00:00 GMT","date":"2024-09-07","time":"12:00:00","weekday":"Saturday"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/number-base-converter":{"post":{"operationId":"number_base_converter","summary":"Number Base Converter","description":"Convert a whole number from one base to another, any base 2 to 36, and also return its binary, octal, decimal and hexadecimal forms. Exact for numbers of any size, no rounding.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string"},"fromBase":{"type":"integer"},"toBase":{"type":"integer"}}},"example":{"value":"255","fromBase":10,"toBase":2}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"string"},"fromBase":{"type":"integer"},"toBase":{"type":"integer"},"output":{"type":"string"},"negative":{"type":"boolean"},"binary":{"type":"string"},"octal":{"type":"string"},"decimal":{"type":"string"},"hexadecimal":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":"255","fromBase":10,"toBase":2,"output":"11111111","negative":false,"binary":"11111111","octal":"377","decimal":"255","hexadecimal":"FF"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/password-generator":{"post":{"operationId":"password_generator","summary":"Password Generator","description":"Generate one or more random passwords of a chosen length from lower case, upper case, digits and symbols, optionally without look-alike characters. Returns the passwords with their entropy in bits and a rough crack time.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"length":{"type":"integer"},"uppercase":{"type":"boolean"},"lowercase":{"type":"boolean"},"numbers":{"type":"boolean"},"symbols":{"type":"boolean"},"excludeAmbiguous":{"type":"boolean"},"count":{"type":"integer"}}},"example":{"length":16,"uppercase":true,"lowercase":true,"numbers":true,"symbols":true,"excludeAmbiguous":false,"count":1}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"passwords":{"type":"array","items":{"type":"string"}},"length":{"type":"integer"},"charsetSize":{"type":"integer"},"entropyBits":{"type":"integer"},"strength":{"type":"string"},"crackTime":{"type":"string"}}}}},"example":{"ok":true,"data":{"passwords":["k7#Qm2$vLp9!Xw4Z"],"length":16,"charsetSize":88,"entropyBits":103,"strength":"Very strong","crackTime":"32B+ years"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/random-number-generator":{"post":{"operationId":"random_number_generator","summary":"Random Number Generator","description":"Draw random whole numbers between a minimum and maximum, inclusive, using cryptographic randomness. Ask for several at once, with or without repeats, for dice, lottery draws or samples.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"},"count":{"type":"integer"},"allowDuplicates":{"type":"boolean"}}},"example":{"min":1,"max":6,"count":3,"allowDuplicates":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"integer"}},"min":{"type":"integer"},"max":{"type":"integer"},"count":{"type":"integer"},"unique":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"numbers":[4,1,6],"min":1,"max":6,"count":3,"unique":true}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/chmod-calculator":{"post":{"operationId":"chmod_calculator","summary":"Chmod Calculator","description":"Convert Unix file permissions between octal (755) and symbolic (rwxr-xr-x) form. Returns both, a read/write/execute breakdown for owner, group and others, and the chmod command to apply them.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"permissions":{"type":"string"}}},"example":{"permissions":"755"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"string"},"octal":{"type":"string"},"symbolic":{"type":"string"},"owner":{"type":"object","properties":{"read":{"type":"boolean"},"write":{"type":"boolean"},"execute":{"type":"boolean"},"octal":{"type":"integer"},"symbolic":{"type":"string"}}},"group":{"type":"object","properties":{"read":{"type":"boolean"},"write":{"type":"boolean"},"execute":{"type":"boolean"},"octal":{"type":"integer"},"symbolic":{"type":"string"}}},"others":{"type":"object","properties":{"read":{"type":"boolean"},"write":{"type":"boolean"},"execute":{"type":"boolean"},"octal":{"type":"integer"},"symbolic":{"type":"string"}}},"command":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":"755","octal":"755","symbolic":"rwxr-xr-x","owner":{"read":true,"write":true,"execute":true,"octal":7,"symbolic":"rwx"},"group":{"read":true,"write":false,"execute":true,"octal":5,"symbolic":"r-x"},"others":{"read":true,"write":false,"execute":true,"octal":5,"symbolic":"r-x"},"command":"chmod 755 <file>"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/http-status-codes":{"post":{"operationId":"http_status_codes","summary":"HTTP Status Codes","description":"Look up what an HTTP status code means. Give a code (404), a class (4xx) or a word (timeout) and get the name, class, one-line meaning, typical use case and RFC reference for every match.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"}}},"example":{"query":"404"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"query":{"type":"string"},"found":{"type":"boolean"},"count":{"type":"integer"},"matches":{"type":"array","items":{"type":"object","properties":{"code":{"type":"integer"},"name":{"type":"string"},"class":{"type":"string"},"className":{"type":"string"},"description":{"type":"string"},"useCase":{"type":"string"},"rfc":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"query":"404","found":true,"count":1,"matches":[{"code":404,"name":"Not Found","class":"4xx","className":"Client Error","description":"The server cannot find the requested resource. The URL is not recognized.","useCase":"Deleted pages, typos in URLs, resources that never existed. Most common HTTP error.","rfc":"RFC 9110"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/px-rem-converter":{"post":{"operationId":"px_rem_converter","summary":"PX to REM Converter","description":"Convert a pixel value to rem or a rem value to pixels for a given root font size, 16px unless told otherwise. Returns both numbers and CSS-ready strings.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"integer"},"direction":{"type":"string"},"rootFontSize":{"type":"integer"}}},"example":{"value":24,"direction":"px-to-rem","rootFontSize":16}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"integer"},"direction":{"type":"string"},"rootFontSize":{"type":"integer"},"px":{"type":"integer"},"rem":{"type":"number"},"pxText":{"type":"string"},"remText":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":24,"direction":"px-to-rem","rootFontSize":16,"px":24,"rem":1.5,"pxText":"24px","remText":"1.5rem"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/utm-builder":{"post":{"operationId":"utm_builder","summary":"UTM Builder","description":"Add utm_source, utm_medium, utm_campaign, utm_term and utm_content to a URL, keeping any query parameters it already has. Returns the tagged link plus warnings about upper case or missing required tags.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"source":{"type":"string"},"medium":{"type":"string"},"campaign":{"type":"string"},"term":{"type":"string"},"content":{"type":"string"}}},"example":{"url":"https://example.com/pricing?ref=1","source":"newsletter","medium":"email","campaign":"spring_sale","term":"running_shoes","content":"header_banner"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"url":{"type":"string"},"baseUrl":{"type":"string"},"params":{"type":"object","properties":{"utm_source":{"type":"string"},"utm_medium":{"type":"string"},"utm_campaign":{"type":"string"},"utm_term":{"type":"string"},"utm_content":{"type":"string"}}},"warnings":{"type":"array","items":{}}}}}},"example":{"ok":true,"data":{"url":"https://example.com/pricing?ref=1&utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale&utm_term=running_shoes&utm_content=header_banner","baseUrl":"https://example.com/pricing?ref=1","params":{"utm_source":"newsletter","utm_medium":"email","utm_campaign":"spring_sale","utm_term":"running_shoes","utm_content":"header_banner"},"warnings":[]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/color-contrast-checker":{"post":{"operationId":"color_contrast_checker","summary":"Color Contrast Checker","description":"Compute the WCAG 2 contrast ratio between a text colour and a background colour and say whether it passes AA and AAA for normal and large text. Accepts hex, rgb() or hsl(), and suggests a nearby colour when it fails.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"foreground":{"type":"string"},"background":{"type":"string"}}},"example":{"foreground":"#1e293b","background":"#ffffff"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"foreground":{"type":"string"},"background":{"type":"string"},"ratio":{"type":"number"},"ratioText":{"type":"string"},"aa":{"type":"object","properties":{"normalText":{"type":"boolean"},"largeText":{"type":"boolean"}}},"aaa":{"type":"object","properties":{"normalText":{"type":"boolean"},"largeText":{"type":"boolean"}}},"suggestions":{"type":"array","items":{}}}}}},"example":{"ok":true,"data":{"foreground":"#1e293b","background":"#ffffff","ratio":14.63,"ratioText":"14.63:1","aa":{"normalText":true,"largeText":true},"aaa":{"normalText":true,"largeText":true},"suggestions":[]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/percentage-calculator":{"post":{"operationId":"percentage_calculator","summary":"Percentage Calculator","description":"Answer one percentage question from two numbers: what x% of y is, what percent x is of y, the percentage change from x to y, or x increased or decreased by y%. Returns the number and a sentence stating it.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"x":{"type":"integer"},"y":{"type":"integer"}}},"example":{"mode":"percentOf","x":15,"y":200}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"x":{"type":"integer"},"y":{"type":"integer"},"result":{"type":"integer"},"resultIsPercent":{"type":"boolean"},"sentence":{"type":"string"}}}}},"example":{"ok":true,"data":{"mode":"percentOf","x":15,"y":200,"result":30,"resultIsPercent":false,"sentence":"15% of 200 is 30."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/compound-interest-calculator":{"post":{"operationId":"compound_interest_calculator","summary":"Compound Interest Calculator","description":"Grow a starting amount at an annual rate for a number of years, compounding monthly, quarterly or annually, with an optional monthly contribution. Returns the future value, total contributed, total interest and a year-by-year table.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"principal":{"type":"integer"},"annualRatePercent":{"type":"integer"},"years":{"type":"integer"},"compoundsPerYear":{"type":"integer"},"monthlyContribution":{"type":"integer"}}},"example":{"principal":10000,"annualRatePercent":7,"years":10,"compoundsPerYear":12,"monthlyContribution":100}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"principal":{"type":"integer"},"annualRatePercent":{"type":"integer"},"years":{"type":"integer"},"compoundsPerYear":{"type":"integer"},"monthlyContribution":{"type":"integer"},"futureValue":{"type":"number"},"totalContributed":{"type":"integer"},"totalInterest":{"type":"number"},"interestPercent":{"type":"number"},"yearly":{"type":"array","items":{"type":"object","properties":{"year":{"type":"integer"},"contributions":{"type":"integer"},"interest":{"type":"number"},"balance":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"principal":10000,"annualRatePercent":7,"years":10,"compoundsPerYear":12,"monthlyContribution":100,"futureValue":37506.06,"totalContributed":22000,"totalInterest":15506.06,"interestPercent":41.34,"yearly":[{"year":1,"contributions":11200,"interest":769.39,"balance":11969.39}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/loan-calculator":{"post":{"operationId":"loan_calculator","summary":"Loan Calculator","description":"Work out an annuity loan from the amount, annual interest rate and term: the fixed monthly payment, total paid, total interest and an amortisation schedule of up to 360 months showing principal, interest and remaining balance.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"annualRatePercent":{"type":"number"},"term":{"type":"integer"},"termUnit":{"type":"string"}}},"example":{"amount":250000,"annualRatePercent":4.5,"term":30,"termUnit":"years"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"amount":{"type":"integer"},"annualRatePercent":{"type":"number"},"termMonths":{"type":"integer"},"monthlyPayment":{"type":"number"},"totalPaid":{"type":"number"},"totalInterest":{"type":"number"},"interestToPrincipalRatio":{"type":"number"},"schedule":{"type":"array","items":{"type":"object","properties":{"month":{"type":"integer"},"payment":{"type":"number"},"principal":{"type":"number"},"interest":{"type":"number"},"balance":{"type":"number"}}}},"scheduleTruncated":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"amount":250000,"annualRatePercent":4.5,"termMonths":360,"monthlyPayment":1266.71,"totalPaid":456016.78,"totalInterest":206016.78,"interestToPrincipalRatio":0.82,"schedule":[{"month":1,"payment":1266.71,"principal":329.21,"interest":937.5,"balance":249670.79}],"scheduleTruncated":false}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/bmi-calculator":{"post":{"operationId":"bmi_calculator","summary":"BMI Calculator","description":"Body mass index from height and weight, in centimetres and kilograms or in inches and pounds. Returns the BMI, its category (underweight, normal weight, overweight, obese) and the weight range that would count as normal at that height.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"height":{"type":"integer"},"weight":{"type":"integer"},"units":{"type":"string"}}},"example":{"height":175,"weight":70,"units":"metric"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"units":{"type":"string"},"heightCm":{"type":"integer"},"weightKg":{"type":"integer"},"bmi":{"type":"number"},"category":{"type":"string"},"healthyWeightRange":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"},"unit":{"type":"string"}}},"categories":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"range":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"units":"metric","heightCm":175,"weightKg":70,"bmi":22.9,"category":"Normal weight","healthyWeightRange":{"min":56.7,"max":76.3,"unit":"kg"},"categories":[{"label":"Underweight","range":"< 18.5"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/date-difference-calculator":{"post":{"operationId":"date_difference_calculator","summary":"Date Difference Calculator","description":"The time between two calendar dates: total days, weeks plus days, a years/months/days breakdown, hours, and the number of business days (Monday to Friday, no holidays). Order does not matter; the end date is excluded unless includeEndDate is true.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"includeEndDate":{"type":"boolean"}}},"example":{"startDate":"2026-01-01","endDate":"2026-12-31","includeEndDate":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"startWeekday":{"type":"string"},"endWeekday":{"type":"string"},"swapped":{"type":"boolean"},"includeEndDate":{"type":"boolean"},"years":{"type":"integer"},"months":{"type":"integer"},"days":{"type":"integer"},"totalDays":{"type":"integer"},"totalWeeks":{"type":"integer"},"weeksAndDays":{"type":"object","properties":{"weeks":{"type":"integer"},"days":{"type":"integer"}}},"totalHours":{"type":"integer"},"businessDays":{"type":"integer"}}}}},"example":{"ok":true,"data":{"start":"2026-01-01","end":"2026-12-31","startWeekday":"Thursday","endWeekday":"Thursday","swapped":false,"includeEndDate":false,"years":0,"months":11,"days":30,"totalDays":364,"totalWeeks":52,"weeksAndDays":{"weeks":52,"days":0},"totalHours":8736,"businessDays":260}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/age-calculator":{"post":{"operationId":"age_calculator","summary":"Age Calculator","description":"Exact age on a date from a birth date: years, months and days, total days, weeks and months lived, and the date of the next birthday with the days until it. Defaults to today when asOf is left out.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"birthDate":{"type":"string"},"asOf":{"type":"string"}}},"example":{"birthDate":"1990-05-15","asOf":"2026-09-07"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"birthDate":{"type":"string"},"asOf":{"type":"string"},"years":{"type":"integer"},"months":{"type":"integer"},"days":{"type":"integer"},"totalDays":{"type":"integer"},"totalWeeks":{"type":"integer"},"totalMonths":{"type":"integer"},"nextBirthday":{"type":"object","properties":{"date":{"type":"string"},"daysUntil":{"type":"integer"},"turning":{"type":"integer"}}}}}}},"example":{"ok":true,"data":{"birthDate":"1990-05-15","asOf":"2026-09-07","years":36,"months":3,"days":23,"totalDays":13264,"totalWeeks":1894,"totalMonths":435,"nextBirthday":{"date":"2027-05-15","daysUntil":250,"turning":37}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/week-number":{"post":{"operationId":"week_number","summary":"Week Number","description":"The ISO 8601 week number and ISO year for a date, with the Monday and Sunday of that week, plus the US (Sunday-based) and UK/EU week numbers. Defaults to today when no date is given.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string"}}},"example":{"date":"2026-09-07"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"date":{"type":"string"},"weekday":{"type":"string"},"isoWeek":{"type":"integer"},"isoYear":{"type":"integer"},"weekStart":{"type":"string"},"weekEnd":{"type":"string"},"usWeek":{"type":"integer"},"ukWeek":{"type":"integer"}}}}},"example":{"ok":true,"data":{"date":"2026-09-07","weekday":"Monday","isoWeek":37,"isoYear":2026,"weekStart":"2026-09-07","weekEnd":"2026-09-13","usWeek":37,"ukWeek":37}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/unit-converter":{"post":{"operationId":"unit_converter","summary":"Unit Converter","description":"Convert a value between units of length (m, km, cm, mm, mi, yd, ft, in, nmi, um), weight (kg, g, mg, lb, oz, st, t, us_ton, imperial_ton, ug), temperature (c, f, k), speed (kmh, ms, mph, kn) or data (b, kb, mb, gb, tb, in 1024 steps). Units are matched by key, full name or common alias.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"integer"},"from":{"type":"string"},"to":{"type":"string"},"category":{"type":"string"}}},"example":{"value":10,"from":"km","to":"mi","category":"length"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"value":{"type":"integer"},"from":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}}},"to":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}}},"category":{"type":"string"},"result":{"type":"number"},"formatted":{"type":"string"}}}}},"example":{"ok":true,"data":{"value":10,"from":{"key":"km","name":"Kilometers (km)"},"to":{"key":"mi","name":"Miles"},"category":"length","result":6.213711922373339,"formatted":"6.213712"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/discount-calculator":{"post":{"operationId":"discount_calculator","summary":"Discount Calculator","description":"Final price and saving after a discount given as a percentage or a fixed amount, with optional extra stacked discounts applied one after another and optional VAT added on the discounted price. Money is rounded to cents.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"price":{"type":"integer"},"discountPercent":{"type":"integer"},"additionalDiscountPercents":{"type":"array","items":{"type":"integer"}},"vatPercent":{"type":"integer"}}},"example":{"price":80,"discountPercent":25,"additionalDiscountPercents":[10],"vatPercent":21}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"originalPrice":{"type":"integer"},"discountPercent":{"type":"number"},"saving":{"type":"integer"},"finalPrice":{"type":"integer"},"vatPercent":{"type":"integer"},"vatAmount":{"type":"number"},"finalPriceWithVat":{"type":"number"},"steps":{"type":"array","items":{"type":"object","properties":{"step":{"type":"integer"},"percent":{"type":"integer"},"priceAfter":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"originalPrice":80,"discountPercent":32.5,"saving":26,"finalPrice":54,"vatPercent":21,"vatAmount":11.34,"finalPriceWithVat":65.34,"steps":[{"step":1,"percent":25,"priceAfter":60},{"step":2,"percent":10,"priceAfter":54}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/tip-calculator":{"post":{"operationId":"tip_calculator","summary":"Tip Calculator","description":"Tip on a restaurant bill at a percentage, the total, and the even split per person. Can round the total up to the next whole unit, in which case the tip absorbs the difference.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bill":{"type":"number"},"tipPercent":{"type":"integer"},"people":{"type":"integer"},"roundUp":{"type":"boolean"}}},"example":{"bill":86.4,"tipPercent":15,"people":4,"roundUp":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"bill":{"type":"number"},"tipPercent":{"type":"integer"},"people":{"type":"integer"},"roundUp":{"type":"boolean"},"tip":{"type":"number"},"total":{"type":"number"},"perPerson":{"type":"object","properties":{"total":{"type":"number"},"tip":{"type":"number"}}},"effectiveTipPercent":{"type":"integer"}}}}},"example":{"ok":true,"data":{"bill":86.4,"tipPercent":15,"people":4,"roundUp":false,"tip":12.96,"total":99.36,"perPerson":{"total":24.84,"tip":3.24},"effectiveTipPercent":15}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/timezone-converter":{"post":{"operationId":"timezone_converter","summary":"Timezone Converter","description":"Take a local date and time in one IANA time zone (such as Europe/Amsterdam) and express it in another, using the built-in Intl zone rules including daylight saving. Returns both wall-clock times with their UTC offsets, the UTC instant and the hour difference.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dateTime":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"}}},"example":{"dateTime":"2026-03-15T14:30","from":"Europe/Amsterdam","to":"America/New_York"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"string"},"from":{"type":"object","properties":{"zone":{"type":"string"},"dateTime":{"type":"string"},"offset":{"type":"string"},"offsetMinutes":{"type":"integer"},"weekday":{"type":"string"}}},"to":{"type":"object","properties":{"zone":{"type":"string"},"dateTime":{"type":"string"},"offset":{"type":"string"},"offsetMinutes":{"type":"integer"},"weekday":{"type":"string"}}},"utc":{"type":"string"},"differenceHours":{"type":"integer"}}}}},"example":{"ok":true,"data":{"input":"2026-03-15T14:30","from":{"zone":"Europe/Amsterdam","dateTime":"2026-03-15T14:30:00","offset":"+01:00","offsetMinutes":60,"weekday":"Sunday"},"to":{"zone":"America/New_York","dateTime":"2026-03-15T09:30:00","offset":"-04:00","offsetMinutes":-240,"weekday":"Sunday"},"utc":"2026-03-15T13:30:00Z","differenceHours":-5}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/roi-calculator":{"post":{"operationId":"roi_calculator","summary":"ROI Calculator","description":"Return on investment from the amount invested and the total amount that came back: the gain, the ROI in percent and, when a holding period in years is given, the annualised return. An estimate, not financial advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"invested":{"type":"integer"},"returned":{"type":"integer"},"years":{"type":"integer"}}},"example":{"invested":10000,"returned":14000,"years":3}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"invested":{"type":"integer"},"returned":{"type":"integer"},"gain":{"type":"integer"},"roiPercent":{"type":"integer"},"years":{"type":"integer"},"annualisedPercent":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"invested":10000,"returned":14000,"gain":4000,"roiPercent":40,"years":3,"annualisedPercent":11.87,"note":"Estimates for information only, not financial advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/break-even-calculator":{"post":{"operationId":"break_even_calculator","summary":"Break-even Calculator","description":"Break-even point from fixed costs per period, the price per unit and the variable cost per unit: the units and revenue needed to cover costs, the contribution margin, profit at an optional sales volume, and scenarios at half to twice the break-even volume.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fixedCosts":{"type":"integer"},"variableCostPerUnit":{"type":"integer"},"pricePerUnit":{"type":"integer"},"volume":{"type":"integer"}}},"example":{"fixedCosts":5000,"variableCostPerUnit":10,"pricePerUnit":25,"volume":500}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"fixedCosts":{"type":"integer"},"pricePerUnit":{"type":"integer"},"variableCostPerUnit":{"type":"integer"},"contributionMargin":{"type":"integer"},"contributionMarginPercent":{"type":"integer"},"breakEvenUnits":{"type":"number"},"breakEvenUnitsRoundedUp":{"type":"integer"},"breakEvenRevenue":{"type":"number"},"volume":{"type":"integer"},"profitAtVolume":{"type":"integer"},"scenarios":{"type":"array","items":{"type":"object","properties":{"multiplier":{"type":"number"},"units":{"type":"integer"},"revenue":{"type":"integer"},"profit":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"fixedCosts":5000,"pricePerUnit":25,"variableCostPerUnit":10,"contributionMargin":15,"contributionMarginPercent":60,"breakEvenUnits":333.33,"breakEvenUnitsRoundedUp":334,"breakEvenRevenue":8333.33,"volume":500,"profitAtVolume":2500,"scenarios":[{"multiplier":0.5,"units":167,"revenue":4175,"profit":-2495}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/aow-age-calculator":{"post":{"operationId":"aow_age_calculator","summary":"AOW Age Calculator","description":"Look up the Dutch state pension (AOW) age for a birth date from the official SVB table: the age in years and months, the date it starts, how long until then, and whether that age is legally fixed or still an expectation.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"birthDate":{"type":"string"},"today":{"type":"string"}}},"example":{"birthDate":"1975-05-12","today":"2026-08-28"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string"},"years":{"type":"integer"},"months":{"type":"integer"},"aowDate":{"type":"string"},"timeLeft":{"type":"object","properties":{"years":{"type":"integer"},"months":{"type":"integer"},"days":{"type":"integer"}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"status":"expected","years":68,"months":0,"aowDate":"2043-05-12","timeLeft":{"years":16,"months":8,"days":15},"note":"This is an expectation, not a guarantee."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/erfbelasting-calculator":{"post":{"operationId":"erfbelasting_calculator","summary":"Dutch Inheritance Tax Calculator","description":"Estimate Dutch inheritance tax (erfbelasting) for one heir with the 2026 exemptions and two-bracket rates: give the inherited amount and the relationship to the deceased, get the exemption, taxable amount, tax per bracket, total tax and what the heir keeps.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"heir":{"type":"string"}}},"example":{"amount":100000,"heir":"child"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"exemption":{"type":"integer"},"exemptionApplied":{"type":"integer"},"taxableAmount":{"type":"integer"},"lowBracketTax":{"type":"integer"},"highBracketTax":{"type":"integer"},"totalTax":{"type":"integer"},"netReceived":{"type":"integer"},"effectiveRate":{"type":"number"},"notes":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"ok":true,"exemption":26230,"exemptionApplied":26230,"taxableAmount":73770,"lowBracketTax":7377,"highBracketTax":0,"totalTax":7377,"netReceived":92623,"effectiveRate":0.07377,"notes":["Partners usually count as one person for inheritance tax."]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/schenkbelasting-calculator":{"post":{"operationId":"schenkbelasting_calculator","summary":"Dutch Gift Tax Calculator","description":"Estimate Dutch gift tax (schenkbelasting) with the 2026 exemptions and two-bracket rates: give the gift amount, the relationship to the donor and which exemption applies, get the exemption used, taxable amount, tax per bracket, total tax and what the recipient keeps.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"relationship":{"type":"string"},"exemption":{"type":"string"}}},"example":{"amount":25000,"relationship":"child","exemption":"annual"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"exemptionApplied":{"type":"integer"},"taxableAmount":{"type":"integer"},"lowBracketTax":{"type":"number"},"highBracketTax":{"type":"integer"},"totalTax":{"type":"number"},"netReceived":{"type":"number"},"effectiveRate":{"type":"number"},"notes":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"ok":true,"exemptionApplied":6908,"taxableAmount":18092,"lowBracketTax":1809.2,"highBracketTax":0,"totalTax":1809.2,"netReceived":23190.8,"effectiveRate":0.072368,"notes":["Partners count as one donor."]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/ww-duration-calculator":{"post":{"operationId":"ww_duration_calculator","summary":"Dutch WW Duration Calculator","description":"Estimate how many months of Dutch unemployment benefit (WW) an employment history gives: one month per year for the first ten years, half a month per year after that, between three and twenty-four months, with the notional years for people born in 1979 or earlier counted automatically.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"birthYear":{"type":"integer"},"workedYearsSince1998":{"type":"integer"}}},"example":{"birthYear":1975,"workedYearsSince1998":20}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"notionalYears":{"type":"integer"},"actualYears":{"type":"integer"},"totalYears":{"type":"integer"},"months":{"type":"number"},"uncappedMonths":{"type":"number"},"cappedAtMaximum":{"type":"boolean"},"raisedToMinimum":{"type":"boolean"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"notionalYears":5,"actualYears":20,"totalYears":25,"months":17.5,"uncappedMonths":17.5,"cappedAtMaximum":false,"raisedToMinimum":false,"note":"This is an estimate."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/30-percent-ruling-calculator":{"post":{"operationId":"30_percent_ruling_calculator","summary":"30% Ruling Calculator","description":"Work out the tax-free allowance under the Dutch 30% ruling for a gross annual salary with the 2026 salary norm and WNT cap, plus a simplified estimate of net pay with and without the ruling and the yearly advantage.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"grossAnnualSalary":{"type":"integer"},"youngMaster":{"type":"boolean"}}},"example":{"grossAnnualSalary":70000,"youngMaster":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"grossAnnualSalary":{"type":"integer"},"salaryNorm":{"type":"integer"},"eligible":{"type":"boolean"},"allowancePerYear":{"type":"integer"},"allowancePerMonth":{"type":"integer"},"taxableSalaryWithRuling":{"type":"integer"},"netPerYearWithoutRuling":{"type":"integer"},"netPerYearWithRuling":{"type":"integer"},"netAdvantagePerYear":{"type":"integer"},"cappedByWnt":{"type":"boolean"},"limitedByNorm":{"type":"boolean"},"notes":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"ok":true,"grossAnnualSalary":70000,"salaryNorm":48013,"eligible":true,"allowancePerYear":21000,"allowancePerMonth":1750,"taxableSalaryWithRuling":49000,"netPerYearWithoutRuling":49000,"netPerYearWithRuling":58000,"netAdvantagePerYear":9000,"cappedByWnt":false,"limitedByNorm":false,"notes":["Based on 2026 figures."]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-holiday-allowance-calculator":{"post":{"operationId":"dutch_holiday_allowance_calculator","summary":"Dutch Holiday Allowance Calculator","description":"Calculate Dutch holiday allowance (vakantiegeld) from a gross monthly salary and the months worked: the gross amount at the given percentage (statutory minimum 8%) and an estimate of the net payout after the special payroll tax rate (bijzonder tarief).","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"monthlySalary":{"type":"integer"},"monthsWorked":{"type":"integer"},"allowancePercent":{"type":"integer"},"specialRatePercent":{"type":"number"}}},"example":{"monthlySalary":3500,"monthsWorked":12,"allowancePercent":8,"specialRatePercent":42.01}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"grossAllowance":{"type":"integer"},"taxWithheld":{"type":"number"},"netAllowance":{"type":"number"},"allowancePercent":{"type":"integer"},"specialRatePercent":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"grossAllowance":3360,"taxWithheld":1411.54,"netAllowance":1948.46,"allowancePercent":8,"specialRatePercent":42.01,"note":"Based on 2026 figures."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-notice-period-calculator":{"post":{"operationId":"dutch_notice_period_calculator","summary":"Dutch Notice Period Calculator","description":"Find the statutory Dutch notice period (Article 7:672 Civil Code) for an employee or employer from the employment start date and the date notice is given, and the date the contract ends, since notice runs from the first of the next month. A contractual period can override the statutory one.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"givenBy":{"type":"string"},"startDate":{"type":"string"},"noticeDate":{"type":"string"},"contractMonths":{"type":"integer"}}},"example":{"givenBy":"employee","startDate":"2019-03-01","noticeDate":"2026-09-12","contractMonths":1}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"serviceYears":{"type":"number"},"statutoryMonths":{"type":"integer"},"appliedMonths":{"type":"integer"},"fromContract":{"type":"boolean"},"noticePeriodStarts":{"type":"string"},"employmentEnds":{"type":"string"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"serviceYears":7.53,"statutoryMonths":1,"appliedMonths":1,"fromContract":true,"noticePeriodStarts":"2026-10-01","employmentEnds":"2026-10-31","note":"Based on Article 7:672 of the Dutch Civil Code."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-severance-calculator":{"post":{"operationId":"dutch_severance_calculator","summary":"Dutch Severance Calculator","description":"Calculate the Dutch statutory transition payment (transitievergoeding) with the 2026 rules: one third of the monthly salary base per year of service, pro rata by day, capped at EUR 102,000 or one annual salary if higher. The base can include 8% holiday allowance and fixed yearly bonuses.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"monthlySalary":{"type":"integer"},"fixedAnnualBonuses":{"type":"integer"},"includeHolidayAllowance":{"type":"boolean"},"startDate":{"type":"string"},"endDate":{"type":"string"}}},"example":{"monthlySalary":3500,"fixedAnnualBonuses":0,"includeHolidayAllowance":true,"startDate":"2018-01-01","endDate":"2026-12-31"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"serviceDays":{"type":"integer"},"serviceYears":{"type":"integer"},"monthlyBase":{"type":"integer"},"formulaAmount":{"type":"number"},"statutoryMaximum":{"type":"integer"},"payment":{"type":"number"},"capped":{"type":"boolean"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"serviceDays":3287,"serviceYears":9,"monthlyBase":3780,"formulaAmount":11339.14,"statutoryMaximum":102000,"payment":11339.14,"capped":false,"note":"Based on 2026 figures."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/test-documentnummer-generator":{"post":{"operationId":"test_documentnummer_generator","summary":"Test Document Number Generator","description":"Generate random Dutch test document numbers in the structural format of an ID card (4 letters + 5 digits), passport (2 letters + 7 digits) or driving licence (10 digits). Format only, no check digit, tied to no real document, for test environments.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"count":{"type":"integer"}}},"example":{"type":"idkaart","count":5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"type":{"type":"string"},"label":{"type":"string"},"format":{"type":"string"},"count":{"type":"integer"},"numbers":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"type":"idkaart","label":"ID Card (Identiteitskaart)","format":"4 letters + 5 digits (e.g. SPFC12345)","count":5,"numbers":["SPFC12345"],"note":"Test numbers only. They follow the structural format of Dutch documents but are random, belong to no real document and carry no check digit."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-school-holidays":{"post":{"operationId":"dutch_school_holidays","summary":"Dutch School Holidays","description":"List the official Dutch school holidays (schoolvakanties) for a school year and region (noord, midden or zuid) with start and end dates, whether each is advisory, days until each one, and which break comes next. Covers 2026-2027 and 2027-2028 from Rijksoverheid.nl.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schoolYear":{"type":"string"},"region":{"type":"string"},"today":{"type":"string"}}},"example":{"schoolYear":"2026-2027","region":"midden","today":"2026-09-07"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"schoolYear":{"type":"string"},"region":{"type":"string"},"holidays":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"dutch":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"},"advisory":{"type":"boolean"},"daysUntilStart":{"type":"integer"},"status":{"type":"string"}}}},"next":{"type":"object","properties":{"name":{"type":"string"},"dutch":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"},"advisory":{"type":"boolean"},"daysUntilStart":{"type":"integer"},"ongoing":{"type":"boolean"}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"schoolYear":"2026-2027","region":"midden","holidays":[{"name":"Autumn break","dutch":"Herfstvakantie","start":"2026-10-17","end":"2026-10-25","advisory":true,"daysUntilStart":40,"status":"upcoming"}],"next":{"name":"Autumn break","dutch":"Herfstvakantie","start":"2026-10-17","end":"2026-10-25","advisory":true,"daysUntilStart":40,"ongoing":false},"note":"Kerstvakantie and zomervakantie are set by the Ministry of Education."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-mileage-calculator":{"post":{"operationId":"dutch_mileage_calculator","summary":"Dutch Mileage Allowance Calculator","description":"Calculate a Dutch kilometre reimbursement per week, month and year from a distance per trip and trips per week (or a weekly total), working weeks and a rate, and show how much of it exceeds the 2026 tax-free maximum of EUR 0.25 per km.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kmPerTrip":{"type":"integer"},"tripsPerWeek":{"type":"integer"},"kmPerWeek":{"type":"integer"},"weeksPerYear":{"type":"integer"},"ratePerKm":{"type":"number"}}},"example":{"kmPerTrip":25,"tripsPerWeek":8,"kmPerWeek":200,"weeksPerYear":46,"ratePerKm":0.25}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"weeklyKm":{"type":"integer"},"yearlyKm":{"type":"integer"},"perWeek":{"type":"integer"},"perMonth":{"type":"number"},"perYear":{"type":"integer"},"ratePerKm":{"type":"number"},"taxFreeRate":{"type":"number"},"aboveTaxFree":{"type":"boolean"},"taxablePartPerYear":{"type":"integer"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"weeklyKm":200,"yearlyKm":9200,"perWeek":50,"perMonth":191.67,"perYear":2300,"ratePerKm":0.25,"taxFreeRate":0.25,"aboveTaxFree":false,"taxablePartPerYear":0,"note":"Based on 2026 figures."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/subtitle-converter":{"post":{"operationId":"subtitle_converter","summary":"Subtitle Converter","description":"Convert subtitles between SRT and WebVTT or to a plain transcript, shift or stretch the timing, append a second part, and repair order, overlaps, empty and flashing cues. Returns the rewritten subtitle text and a report of what changed.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"format":{"type":"string"},"shiftSeconds":{"type":"number"},"scalePercent":{"type":"number"},"repair":{"type":"boolean"},"appendText":{"type":"string"},"appendOffsetSeconds":{"type":"integer"}}},"example":{"text":"1\n00:00:01,000 --> 00:00:03,500\nHello there.","format":"vtt","shiftSeconds":-2.5,"scalePercent":104.271,"repair":true,"appendText":"1\n00:00:00,500 --> 00:00:02,000\nPart two begins.","appendOffsetSeconds":5400}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"format":{"type":"string"},"inputFormat":{"type":"string"},"cues":{"type":"integer"},"durationMs":{"type":"integer"},"notes":{"type":"array","items":{}},"repairs":{"type":"object","properties":{"reordered":{"type":"boolean"},"emptyRemoved":{"type":"integer"},"overlapsFixed":{"type":"integer"},"negativeClamped":{"type":"integer"},"tooShortExtended":{"type":"integer"}}}}}}},"example":{"ok":true,"data":{"result":"WEBVTT\n\n00:00:01.000 --> 00:00:03.500\nHello there.\n","format":"vtt","inputFormat":"srt","cues":1,"durationMs":3500,"notes":[],"repairs":{"reordered":false,"emptyRemoved":0,"overlapsFixed":0,"negativeClamped":0,"tooShortExtended":0}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/csv-merge":{"post":{"operationId":"csv_merge","summary":"CSV Merge","description":"Append several CSV files into one. By header, columns are matched by name (case-insensitive) and the headers are unioned, so files with different column orders or an extra column combine cleanly; by position, rows are appended under the first file's header. Optionally adds a Source column with each file's name. Returns the merged CSV.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"text":{"type":"string"}}}},"mode":{"type":"string"},"sourceColumn":{"type":"boolean"},"delimiter":{"type":"string"}}},"example":{"files":[{"name":"jan.csv","text":"id,amount\n1,10\n"},{"name":"feb.csv","text":"amount,id\n20,2\n"}],"mode":"byHeader","sourceColumn":true,"delimiter":";"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"header":{"type":"array","items":{"type":"string"}},"rows":{"type":"integer"},"files":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"id,amount,Source\n1,10,jan.csv\n2,20,feb.csv\n","header":["id","amount","Source"],"rows":2,"files":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/csv-split":{"post":{"operationId":"csv_split","summary":"CSV Split","description":"Split one CSV into parts, either every N rows or one part per distinct value of a column. Every part keeps the header. Returns the parts as CSV text with a name each.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"by":{"type":"string"},"size":{"type":"integer"},"column":{"type":"string"},"name":{"type":"string"}}},"example":{"text":"id,country\n1,NL\n2,BE\n3,NL\n","by":"column","size":1000,"column":"country","name":"orders"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"parts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"rows":{"type":"integer"},"csv":{"type":"string"}}}},"count":{"type":"integer"},"totalRows":{"type":"integer"}}}}},"example":{"ok":true,"data":{"parts":[{"name":"orders-NL","rows":2,"csv":"id,country\n1,NL\n3,NL\n"},{"name":"orders-BE","rows":1,"csv":"id,country\n2,BE\n"}],"count":2,"totalRows":3}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/mortgage-calculator":{"post":{"operationId":"mortgage_calculator","summary":"Mortgage Calculator","description":"Work out the payment on a home loan from the price, the deposit, the rate and the term, paid monthly, bi-weekly or weekly. Returns the payment per period, the total paid, the total interest, the loan-to-value ratio and an amortisation schedule of up to 360 periods showing how each payment splits between principal and interest.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"homePrice":{"type":"integer"},"downPayment":{"type":"integer"},"annualRatePercent":{"type":"number"},"termYears":{"type":"integer"},"paymentFrequency":{"type":"string"}}},"example":{"homePrice":350000,"downPayment":70000,"annualRatePercent":4.5,"termYears":30,"paymentFrequency":"monthly"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"homePrice":{"type":"integer"},"downPayment":{"type":"integer"},"loanAmount":{"type":"integer"},"annualRatePercent":{"type":"number"},"termYears":{"type":"integer"},"frequency":{"type":"string"},"periodsPerYear":{"type":"integer"},"totalPeriods":{"type":"integer"},"periodicPayment":{"type":"number"},"totalPayment":{"type":"number"},"totalInterest":{"type":"number"},"loanToValuePercent":{"type":"integer"},"schedule":{"type":"array","items":{"type":"object","properties":{"period":{"type":"integer"},"payment":{"type":"number"},"principal":{"type":"number"},"interest":{"type":"integer"},"balance":{"type":"number"}}}},"scheduleTruncated":{"type":"boolean"}}}}},"example":{"ok":true,"data":{"homePrice":350000,"downPayment":70000,"loanAmount":280000,"annualRatePercent":4.5,"termYears":30,"frequency":"monthly","periodsPerYear":12,"totalPeriods":360,"periodicPayment":1418.72,"totalPayment":510738.79,"totalInterest":230738.79,"loanToValuePercent":80,"schedule":[{"period":1,"payment":1418.72,"principal":368.72,"interest":1050,"balance":279631.28}],"scheduleTruncated":false}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/simple-interest-calculator":{"post":{"operationId":"simple_interest_calculator","summary":"Simple Interest Calculator","description":"Interest at a flat rate on the original principal only, I = P x r x t, over a period given in years or months. Returns the interest, the final amount, and what the same money would have made with annual compounding, so the two methods can be compared before signing anything.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"principal":{"type":"integer"},"annualRatePercent":{"type":"integer"},"time":{"type":"integer"},"timeUnit":{"type":"string"}}},"example":{"principal":10000,"annualRatePercent":5,"time":3,"timeUnit":"years"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"principal":{"type":"integer"},"annualRatePercent":{"type":"integer"},"time":{"type":"integer"},"timeUnit":{"type":"string"},"timeYears":{"type":"integer"},"interest":{"type":"integer"},"totalAmount":{"type":"integer"},"compoundTotalAmount":{"type":"number"},"compoundInterest":{"type":"number"},"compoundAdvantage":{"type":"number"}}}}},"example":{"ok":true,"data":{"principal":10000,"annualRatePercent":5,"time":3,"timeUnit":"years","timeYears":3,"interest":1500,"totalAmount":11500,"compoundTotalAmount":11576.25,"compoundInterest":1576.25,"compoundAdvantage":76.25}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/investment-calculator":{"post":{"operationId":"investment_calculator","summary":"Investment Calculator","description":"Project a lump sum plus a monthly contribution at a yearly return, compounded monthly with the contribution going in at the start of each month. Returns the final value, everything paid in, the growth, and a year-by-year table of balance and growth.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"initialInvestment":{"type":"integer"},"monthlyContribution":{"type":"integer"},"annualReturnPercent":{"type":"integer"},"years":{"type":"integer"}}},"example":{"initialInvestment":10000,"monthlyContribution":500,"annualReturnPercent":7,"years":20}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"initialInvestment":{"type":"integer"},"monthlyContribution":{"type":"integer"},"annualReturnPercent":{"type":"integer"},"years":{"type":"integer"},"finalValue":{"type":"number"},"totalContributed":{"type":"integer"},"totalGrowth":{"type":"number"},"growthPercent":{"type":"integer"},"yearlyBreakdown":{"type":"array","items":{"type":"object","properties":{"year":{"type":"integer"},"contributed":{"type":"integer"},"growth":{"type":"number"},"balance":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"initialInvestment":10000,"monthlyContribution":500,"annualReturnPercent":7,"years":20,"finalValue":302370.09,"totalContributed":130000,"totalGrowth":172370.09,"growthPercent":57,"yearlyBreakdown":[{"year":1,"contributed":16000,"growth":955.34,"balance":16955.34}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/sip-calculator":{"post":{"operationId":"sip_calculator","summary":"SIP Calculator","description":"Returns on a systematic investment plan: the same amount invested every month for a number of years at an expected annual return, using the annuity due formula Indian SIP calculators publish. Returns the total value, the amount invested, the returns earned and a year-by-year table.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"monthlyInvestment":{"type":"integer"},"expectedAnnualReturnPercent":{"type":"integer"},"years":{"type":"integer"}}},"example":{"monthlyInvestment":5000,"expectedAnnualReturnPercent":12,"years":10}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"monthlyInvestment":{"type":"integer"},"expectedAnnualReturnPercent":{"type":"integer"},"years":{"type":"integer"},"totalValue":{"type":"number"},"investedAmount":{"type":"integer"},"estimatedReturns":{"type":"number"},"returnsPercent":{"type":"number"},"yearlyBreakdown":{"type":"array","items":{"type":"object","properties":{"year":{"type":"integer"},"invested":{"type":"integer"},"returns":{"type":"number"},"value":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"monthlyInvestment":5000,"expectedAnnualReturnPercent":12,"years":10,"totalValue":1161695.38,"investedAmount":600000,"estimatedReturns":561695.38,"returnsPercent":48.4,"yearlyBreakdown":[{"year":1,"invested":60000,"returns":4046.64,"value":64046.64}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/emi-calculator":{"post":{"operationId":"emi_calculator","summary":"EMI Calculator","description":"The equated monthly instalment on a loan from the amount, the annual rate and a tenure in years or months. Returns the instalment, the total paid, the total interest, interest as a share of the total, and an amortisation schedule grouped by year.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loanAmount":{"type":"integer"},"annualRatePercent":{"type":"number"},"tenure":{"type":"integer"},"tenureUnit":{"type":"string"}}},"example":{"loanAmount":1000000,"annualRatePercent":8.5,"tenure":20,"tenureUnit":"years"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"loanAmount":{"type":"integer"},"annualRatePercent":{"type":"number"},"tenure":{"type":"integer"},"tenureUnit":{"type":"string"},"months":{"type":"integer"},"emi":{"type":"number"},"totalPayment":{"type":"number"},"totalInterest":{"type":"number"},"interestPercent":{"type":"integer"},"yearlyBreakdown":{"type":"array","items":{"type":"object","properties":{"year":{"type":"integer"},"principalPaid":{"type":"number"},"interestPaid":{"type":"number"},"balance":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"loanAmount":1000000,"annualRatePercent":8.5,"tenure":20,"tenureUnit":"years","months":240,"emi":8678.23,"totalPayment":2082775.76,"totalInterest":1082775.76,"interestPercent":52,"yearlyBreakdown":[{"year":1,"principalPaid":19902.29,"interestPaid":84236.5,"balance":980097.71}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/profit-margin-calculator":{"post":{"operationId":"profit_margin_calculator","summary":"Profit Margin Calculator","description":"Gross margin, markup and profit on one sale. In margin mode give the cost and the selling price; in price mode give the cost and the margin you want and it returns the price to charge. Margin is profit over the price, markup is profit over the cost, and both come back either way.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"cost":{"type":"integer"},"revenue":{"type":"integer"},"targetMarginPercent":{"type":"integer"}}},"example":{"mode":"margin","cost":60,"revenue":100,"targetMarginPercent":40}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"cost":{"type":"integer"},"revenue":{"type":"integer"},"profit":{"type":"integer"},"grossMarginPercent":{"type":"integer"},"markupPercent":{"type":"number"}}}}},"example":{"ok":true,"data":{"mode":"margin","cost":60,"revenue":100,"profit":40,"grossMarginPercent":40,"markupPercent":66.67}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/margin-calculator":{"post":{"operationId":"margin_calculator","summary":"Margin and Markup Calculator","description":"Give any two of cost, revenue and gross margin and this solves for the rest, including the markup. Use costRevenue when you know both amounts, costMargin to price up from a cost, and revenueMargin to work out what a product may cost to hit a margin at a fixed price.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"cost":{"type":"integer"},"revenue":{"type":"integer"},"marginPercent":{"type":"integer"}}},"example":{"mode":"costRevenue","cost":60,"revenue":100,"marginPercent":40}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"cost":{"type":"integer"},"revenue":{"type":"integer"},"profit":{"type":"integer"},"grossMarginPercent":{"type":"integer"},"markupPercent":{"type":"number"}}}}},"example":{"ok":true,"data":{"mode":"costRevenue","cost":60,"revenue":100,"profit":40,"grossMarginPercent":40,"markupPercent":66.67}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/commission-calculator":{"post":{"operationId":"commission_calculator","summary":"Commission Calculator","description":"Commission on a sale and what is left after it. Flat mode pays one rate on the whole amount; tiered mode works like tax brackets, each rate paid only on the slice of the sale inside its band, which is why the effective rate lands between the lowest and highest tier.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"saleAmount":{"type":"integer"},"mode":{"type":"string"},"ratePercent":{"type":"integer"},"tiers":{"type":"array","items":{"type":"object","properties":{"upTo":{"type":"integer"},"ratePercent":{"type":"integer"}}}}}},"example":{"saleAmount":50000,"mode":"tiered","ratePercent":5,"tiers":[{"upTo":10000,"ratePercent":3},{"upTo":30000,"ratePercent":5},{"ratePercent":8}]}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"saleAmount":{"type":"integer"},"commission":{"type":"integer"},"net":{"type":"integer"},"effectiveRatePercent":{"type":"number"},"breakdown":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"amount":{"type":"integer"},"ratePercent":{"type":"integer"},"commission":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"mode":"tiered","saleAmount":50000,"commission":2900,"net":47100,"effectiveRatePercent":5.8,"breakdown":[{"label":"Up to 10,000","amount":10000,"ratePercent":3,"commission":300}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/sales-tax-calculator":{"post":{"operationId":"sales_tax_calculator","summary":"Sales Tax Calculator","description":"Add sales tax to a net price, or pull it back out of a price that already includes it. Works for US sales tax and for VAT alike. Returns the net, the tax and the gross, rounded so that net plus tax is exactly the gross.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"ratePercent":{"type":"number"},"mode":{"type":"string"}}},"example":{"amount":100,"ratePercent":8.875,"mode":"add"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"ratePercent":{"type":"number"},"net":{"type":"integer"},"tax":{"type":"number"},"gross":{"type":"number"}}}}},"example":{"ok":true,"data":{"mode":"add","ratePercent":8.875,"net":100,"tax":8.88,"gross":108.88}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/gst-calculator":{"post":{"operationId":"gst_calculator","summary":"GST Calculator","description":"Add GST to a base price or remove it from an inclusive one, at any rate including the Indian slabs of 5, 12, 18 and 28 percent. Returns the net, the GST and the gross, plus the CGST and SGST halves for an intra-state supply, split so the two halves add up to the tax exactly.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"ratePercent":{"type":"integer"},"mode":{"type":"string"}}},"example":{"amount":10000,"ratePercent":18,"mode":"add"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"ratePercent":{"type":"integer"},"net":{"type":"integer"},"gst":{"type":"integer"},"gross":{"type":"integer"},"cgst":{"type":"integer"},"sgst":{"type":"integer"}}}}},"example":{"ok":true,"data":{"mode":"add","ratePercent":18,"net":10000,"gst":1800,"gross":11800,"cgst":900,"sgst":900}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/inflation-calculator":{"post":{"operationId":"inflation_calculator","summary":"Inflation Calculator","description":"What an amount is worth after inflation, or what it was worth before. Fixed mode applies a rate you choose over a number of years. CPI mode uses the published US CPI-U annual averages from 1913 to 2025, so two years can be compared with the figures the BLS actually reported.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"amount":{"type":"integer"},"annualRatePercent":{"type":"integer"},"years":{"type":"integer"},"fromYear":{"type":"integer"},"toYear":{"type":"integer"}}},"example":{"mode":"cpi","amount":100,"annualRatePercent":3,"years":10,"fromYear":1990,"toYear":2025}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"amount":{"type":"integer"},"years":{"type":"integer"},"annualRatePercent":{"type":"number"},"futureCost":{"type":"number"},"purchasingPower":{"type":"number"},"cumulativeInflationPercent":{"type":"number"},"fromYear":{"type":"integer"},"toYear":{"type":"integer"},"fromIndex":{"type":"number"},"toIndex":{"type":"integer"}}}}},"example":{"ok":true,"data":{"mode":"cpi","amount":100,"years":35,"annualRatePercent":2.61,"futureCost":246.37,"purchasingPower":40.59,"cumulativeInflationPercent":146.37,"fromYear":1990,"toYear":2025,"fromIndex":130.7,"toIndex":322}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/retirement-calculator":{"post":{"operationId":"retirement_calculator","summary":"Retirement Calculator","description":"Project a pension pot from what is saved now, what goes in every month, the return and the years left until retirement. Returns the pot at the retirement age, the same pot restated in today's money at an inflation rate, what the 4% rule would pay per month, and a year-by-year table.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"currentAge":{"type":"integer"},"retirementAge":{"type":"integer"},"currentSavings":{"type":"integer"},"monthlyContribution":{"type":"integer"},"annualReturnPercent":{"type":"integer"},"annualInflationPercent":{"type":"number"}}},"example":{"currentAge":30,"retirementAge":67,"currentSavings":25000,"monthlyContribution":500,"annualReturnPercent":7,"annualInflationPercent":2.5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"currentAge":{"type":"integer"},"retirementAge":{"type":"integer"},"yearsToRetire":{"type":"integer"},"currentSavings":{"type":"integer"},"monthlyContribution":{"type":"integer"},"annualReturnPercent":{"type":"integer"},"annualInflationPercent":{"type":"number"},"nestEgg":{"type":"number"},"nestEggInTodaysMoney":{"type":"number"},"totalContributed":{"type":"integer"},"totalGrowth":{"type":"number"},"monthlyIncome":{"type":"number"},"monthlyIncomeInTodaysMoney":{"type":"number"},"yearlyBreakdown":{"type":"array","items":{"type":"object","properties":{"age":{"type":"integer"},"contributed":{"type":"integer"},"balance":{"type":"number"},"todaysValue":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"currentAge":30,"retirementAge":67,"yearsToRetire":37,"currentSavings":25000,"monthlyContribution":500,"annualReturnPercent":7,"annualInflationPercent":2.5,"nestEgg":1385133.21,"nestEggInTodaysMoney":555531.29,"totalContributed":247000,"totalGrowth":1138133.21,"monthlyIncome":4617.11,"monthlyIncomeInTodaysMoney":1851.77,"yearlyBreakdown":[{"age":31,"contributed":31000,"balance":33039.69,"todaysValue":32233.84}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/debt-payoff-calculator":{"post":{"operationId":"debt_payoff_calculator","summary":"Debt Payoff Calculator","description":"Take a list of debts with balance, APR and minimum payment, add whatever extra you can pay each month, and simulate both payoff strategies: snowball, which clears the smallest balance first, and avalanche, which clears the dearest first. Returns months to debt free, total interest and the payoff order for each, plus what avalanche saves.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"debts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"balance":{"type":"integer"},"aprPercent":{"type":"integer"},"minPayment":{"type":"integer"}}}},"extraMonthlyPayment":{"type":"integer"}}},"example":{"debts":[{"name":"Credit card","balance":3500,"aprPercent":21,"minPayment":90},{"name":"Car loan","balance":9000,"aprPercent":7.5,"minPayment":260}],"extraMonthlyPayment":200}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"totalBalance":{"type":"integer"},"totalMinimumPayment":{"type":"integer"},"extraMonthlyPayment":{"type":"integer"},"snowball":{"type":"object","properties":{"strategy":{"type":"string"},"months":{"type":"integer"},"totalInterest":{"type":"number"},"payoffOrder":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"month":{"type":"integer"}}}},"paidOff":{"type":"boolean"}}},"avalanche":{"type":"object","properties":{"strategy":{"type":"string"},"months":{"type":"integer"},"totalInterest":{"type":"number"},"payoffOrder":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"month":{"type":"integer"}}}},"paidOff":{"type":"boolean"}}},"interestSavedByAvalanche":{"type":"integer"},"monthsSavedByAvalanche":{"type":"integer"},"underwater":{"type":"array","items":{}}}}}},"example":{"ok":true,"data":{"totalBalance":12500,"totalMinimumPayment":350,"extraMonthlyPayment":200,"snowball":{"strategy":"snowball","months":28,"totalInterest":1407.79,"payoffOrder":[{"name":"Credit card","month":14}],"paidOff":true},"avalanche":{"strategy":"avalanche","months":28,"totalInterest":1407.79,"payoffOrder":[{"name":"Credit card","month":14}],"paidOff":true},"interestSavedByAvalanche":0,"monthsSavedByAvalanche":0,"underwater":[]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/savings-goal-calculator":{"post":{"operationId":"savings_goal_calculator","summary":"Savings Goal Calculator","description":"A savings goal from either end. monthsToGoal walks the months at a given deposit until the balance clears the goal and reports the quarter marks along the way; requiredDeposit solves the other way round for the monthly amount a deadline needs. Both include interest compounded monthly.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"goal":{"type":"integer"},"currentSavings":{"type":"integer"},"monthlyDeposit":{"type":"integer"},"months":{"type":"integer"},"annualRatePercent":{"type":"number"}}},"example":{"mode":"monthsToGoal","goal":10000,"currentSavings":1500,"monthlyDeposit":300,"months":24,"annualRatePercent":2.5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"goal":{"type":"integer"},"currentSavings":{"type":"integer"},"annualRatePercent":{"type":"number"},"alreadyThere":{"type":"boolean"},"months":{"type":"integer"},"monthlyDeposit":{"type":"integer"},"totalDeposited":{"type":"integer"},"interestEarned":{"type":"number"},"finalBalance":{"type":"number"},"schedule":{"type":"array","items":{"type":"object","properties":{"month":{"type":"integer"},"balance":{"type":"number"},"note":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"mode":"monthsToGoal","goal":10000,"currentSavings":1500,"annualRatePercent":2.5,"alreadyThere":false,"months":28,"monthlyDeposit":300,"totalDeposited":8400,"interestEarned":330.58,"finalBalance":10230.58,"schedule":[{"month":1,"balance":1803.13,"note":""}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/split-bill-calculator":{"post":{"operationId":"split_bill_calculator","summary":"Split Bill Calculator","description":"Settle up a group who have been paying for each other. Each expense is paid by one person and shared evenly by everyone or by the people named on it, with an optional tip added first. Returns what each person paid, owes and is owed, plus the shortest set of transfers that clears the group, optionally rounded to whole notes.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"people":{"type":"array","items":{"type":"string"}},"expenses":{"type":"array","items":{"type":"object","properties":{"payer":{"type":"string"},"description":{"type":"string"},"amount":{"type":"integer"}}}},"tipPercent":{"type":"integer"},"roundTransfersTo":{"type":"integer"}}},"example":{"people":["Alex","Sam","Kim"],"expenses":[{"payer":"Alex","description":"Dinner","amount":84},{"payer":"Sam","description":"Taxi","amount":21}],"tipPercent":10,"roundTransfersTo":5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"people":{"type":"array","items":{"type":"string"}},"total":{"type":"integer"},"tipPercent":{"type":"integer"},"balances":{"type":"array","items":{"type":"object","properties":{"person":{"type":"string"},"paid":{"type":"integer"},"owed":{"type":"integer"},"balance":{"type":"integer"}}}},"transfers":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"amount":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"people":["Alex","Sam","Kim"],"total":105,"tipPercent":0,"balances":[{"person":"Alex","paid":84,"owed":35,"balance":49}],"transfers":[{"from":"Kim","to":"Alex","amount":35}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/freelance-rate-calculator":{"post":{"operationId":"freelance_rate_calculator","summary":"Freelance Rate Calculator","description":"The hourly and day rate a Dutch freelancer has to charge to take home a target net income. Works backwards from the net through the 2026 box 1 brackets, the zelfstandigenaftrek, the mkb-winstvrijstelling, the tax credits and the Zvw contribution, adds business costs and a buffer, and divides by the hours left after holidays and non-billable time. Dutch ZZP rules only.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"targetNetIncome":{"type":"integer"},"businessCosts":{"type":"integer"},"nonBillablePercent":{"type":"integer"},"vacationWeeks":{"type":"integer"},"hoursPerWeek":{"type":"integer"},"bufferPercent":{"type":"integer"},"monthlyDisabilityInsurance":{"type":"integer"},"monthlyPensionDeposit":{"type":"integer"},"hoursPerDay":{"type":"integer"}}},"example":{"targetNetIncome":45000,"businessCosts":5000,"nonBillablePercent":30,"vacationWeeks":5,"hoursPerWeek":40,"bufferPercent":10,"monthlyDisabilityInsurance":150,"monthlyPensionDeposit":250,"hoursPerDay":8}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"targetNetIncome":{"type":"integer"},"billableHours":{"type":"integer"},"hourlyRate":{"type":"number"},"dayRate":{"type":"integer"},"hoursPerDay":{"type":"integer"},"revenue":{"type":"number"},"revenueWithBuffer":{"type":"number"},"buffer":{"type":"number"},"costs":{"type":"integer"},"profit":{"type":"number"},"incomeTax":{"type":"number"},"healthcareContribution":{"type":"number"},"disabilityInsurance":{"type":"integer"},"pensionDeposit":{"type":"integer"},"equivalentEmployeeSalary":{"type":"number"}}}}},"example":{"ok":true,"data":{"targetNetIncome":45000,"billableHours":1316,"hourlyRate":52.75,"dayRate":422,"hoursPerDay":8,"revenue":63108.06,"revenueWithBuffer":69418.86,"buffer":6310.81,"costs":5000,"profit":58108.06,"incomeTax":10698.54,"healthcareContribution":2409.52,"disabilityInsurance":0,"pensionDeposit":0,"equivalentEmployeeSalary":61830.07}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/macro-calculator":{"post":{"operationId":"macro_calculator","summary":"Macro Calculator","description":"Daily protein, carbohydrate and fat grams for a calorie target and a goal. Calories come from the Mifflin-St Jeor basal rate times an activity factor, shifted by 500 kcal down to lose or 300 kcal up to gain and never below 1200, and are then split three ways: balanced 30/40/30, low carb 40/20/40 and high protein 40/30/30. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"age":{"type":"integer"},"sex":{"type":"string"},"heightCm":{"type":"integer"},"weightKg":{"type":"integer"},"activity":{"type":"string"},"goal":{"type":"string"}}},"example":{"age":30,"sex":"male","heightCm":178,"weightKg":75,"activity":"moderate","goal":"maintain"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"bmr":{"type":"integer"},"tdee":{"type":"integer"},"calories":{"type":"integer"},"goal":{"type":"string"},"activity":{"type":"string"},"activityFactor":{"type":"number"},"formula":{"type":"string"},"splits":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"protein":{"type":"integer"},"carbs":{"type":"integer"},"fat":{"type":"integer"},"proteinG":{"type":"integer"},"carbsG":{"type":"integer"},"fatG":{"type":"integer"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"bmr":1718,"tdee":2662,"calories":2662,"goal":"maintain","activity":"moderate","activityFactor":1.55,"formula":"Mifflin-St Jeor (1990)","splits":[{"id":"balanced","label":"Balanced","protein":30,"carbs":40,"fat":30,"proteinG":200,"carbsG":266,"fatG":89}],"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/sleep-calculator":{"post":{"operationId":"sleep_calculator","summary":"Sleep Calculator","description":"Bedtimes or wake-up times that complete whole sleep cycles. From one clock time it counts six, five, four and three blocks of 90 minutes, plus the 15 minutes an average adult needs to fall asleep, so the alarm lands between cycles instead of in deep sleep. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"time":{"type":"string"},"mode":{"type":"string"}}},"example":{"time":"07:00","mode":"wake"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"time":{"type":"string"},"cycleMinutes":{"type":"integer"},"fallAsleepMinutes":{"type":"integer"},"options":{"type":"array","items":{"type":"object","properties":{"cycles":{"type":"integer"},"time":{"type":"string"},"hours":{"type":"integer"},"quality":{"type":"string"},"tone":{"type":"string"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"mode":"wake","time":"07:00","cycleMinutes":90,"fallAsleepMinutes":15,"options":[{"cycles":6,"time":"9:45 PM","hours":9,"quality":"Optimal (9 h sleep)","tone":"optimal"}],"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/water-intake-calculator":{"post":{"operationId":"water_intake_calculator","summary":"Water Intake Calculator","description":"Daily water intake from weight, activity and climate. The base is 35 ml per kilogram of body weight, the midpoint of the usual 30 to 40 ml guideline, plus 350 ml for every 30 minutes of exercise and a flat 500 ml in a hot or humid climate, reported in litres and in 250 ml glasses. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weightKg":{"type":"integer"},"exerciseMinutes":{"type":"integer"},"climate":{"type":"string"}}},"example":{"weightKg":75,"exerciseMinutes":30,"climate":"normal"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"weightKg":{"type":"integer"},"exerciseMinutes":{"type":"integer"},"climate":{"type":"string"},"baseL":{"type":"number"},"exerciseL":{"type":"number"},"climateL":{"type":"integer"},"totalL":{"type":"number"},"glasses":{"type":"integer"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"weightKg":75,"exerciseMinutes":30,"climate":"normal","baseL":2.625,"exerciseL":0.35,"climateL":0,"totalL":2.975,"glasses":12,"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/body-fat-calculator":{"post":{"operationId":"body_fat_calculator","summary":"Body Fat Calculator","description":"Body fat percentage from tape measurements using the US Navy method. The Hodgdon and Beckett (1984) circumference formula takes height, neck and waist for men and adds the hip for women, returns the American Council on Exercise category band, and splits a body weight into fat and lean mass when one is given. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sex":{"type":"string"},"heightCm":{"type":"integer"},"neckCm":{"type":"integer"},"waistCm":{"type":"integer"},"hipCm":{"type":"integer"},"weightKg":{"type":"integer"}}},"example":{"sex":"male","heightCm":178,"neckCm":38,"waistCm":85,"hipCm":95,"weightKg":80}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sex":{"type":"string"},"bodyFatPercent":{"type":"number"},"category":{"type":"string"},"fatMassKg":{"type":"number"},"leanMassKg":{"type":"number"},"formula":{"type":"string"},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"male":{"type":"array","items":{"type":"integer"}},"female":{"type":"array","items":{"type":"integer"}}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"sex":"male","bodyFatPercent":16.44,"category":"Fitness","fatMassKg":13.15,"leanMassKg":66.85,"formula":"US Navy circumference method (Hodgdon and Beckett, 1984)","categories":[{"id":"fit","label":"Fitness","male":[14,17],"female":[21,24]}],"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/bmr-calculator":{"post":{"operationId":"bmr_calculator","summary":"BMR Calculator","description":"Basal metabolic rate from sex, age, height and weight, by both Mifflin-St Jeor (1990) and the revised Harris-Benedict (Roza and Shizgal, 1984), so the spread between them shows the uncertainty. Also returns total daily energy expenditure at all five activity factors, from 1.2 sedentary to 1.9 extra active. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sex":{"type":"string"},"age":{"type":"integer"},"heightCm":{"type":"integer"},"weightKg":{"type":"integer"}}},"example":{"sex":"male","age":30,"heightCm":178,"weightKg":75}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sex":{"type":"string"},"age":{"type":"integer"},"mifflin":{"type":"integer"},"harris":{"type":"integer"},"formulas":{"type":"array","items":{"type":"string"}},"tdee":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"factor":{"type":"number"},"mifflin":{"type":"integer"},"harris":{"type":"integer"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"sex":"male","age":30,"mifflin":1718,"harris":1777,"formulas":["Mifflin-St Jeor (1990)","Harris-Benedict revised (1984)"],"tdee":[{"id":"moderate","label":"Moderately active (3-5 days/week)","factor":1.55,"mifflin":2662,"harris":2754}],"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/ideal-weight-calculator":{"post":{"operationId":"ideal_weight_calculator","summary":"Ideal Weight Calculator","description":"Ideal body weight from height and sex by the common formulas: Robinson (1983), Miller (1983), Devine (1974) and Hamwi (1964), with their average and the WHO healthy BMI band of 18.5 to 24.9 for the same height, all in kilograms and pounds. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"heightCm":{"type":"integer"},"sex":{"type":"string"}}},"example":{"heightCm":178,"sex":"male"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sex":{"type":"string"},"heightCm":{"type":"integer"},"formulas":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"kg":{"type":"number"},"lb":{"type":"number"}}}},"averageKg":{"type":"number"},"averageLb":{"type":"number"},"healthyBmiRange":{"type":"object","properties":{"minKg":{"type":"number"},"maxKg":{"type":"number"},"minLb":{"type":"number"},"maxLb":{"type":"number"}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"sex":"male","heightCm":178,"formulas":[{"name":"Robinson (1983)","kg":71.15,"lb":156.86}],"averageKg":72.49,"averageLb":159.81,"healthyBmiRange":{"minKg":58.61,"maxKg":78.89,"minLb":129.21,"maxLb":173.92},"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/calorie-calculator":{"post":{"operationId":"calorie_calculator","summary":"Calorie Calculator","description":"Daily calories to maintain, lose or gain weight from your details and activity. Mifflin-St Jeor (1990) sets the basal rate and drives the targets, revised Harris-Benedict (1984) is returned beside it for comparison, and the maintenance figure is also split into the balanced 30/40/30 macros. Consult a registered dietitian for personalized nutrition advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sex":{"type":"string"},"age":{"type":"integer"},"heightCm":{"type":"integer"},"weightKg":{"type":"integer"},"activity":{"type":"string"}}},"example":{"sex":"male","age":30,"heightCm":178,"weightKg":75,"activity":"moderate"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sex":{"type":"string"},"activity":{"type":"string"},"activityFactor":{"type":"number"},"bmrMifflin":{"type":"integer"},"bmrHarris":{"type":"integer"},"tdeeMifflin":{"type":"integer"},"tdeeHarris":{"type":"integer"},"maintenance":{"type":"integer"},"weightLoss":{"type":"integer"},"weightGain":{"type":"integer"},"macros":{"type":"object","properties":{"protein":{"type":"integer"},"carbs":{"type":"integer"},"fat":{"type":"integer"}}},"formulas":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"sex":"male","activity":"moderate","activityFactor":1.55,"bmrMifflin":1718,"bmrHarris":1777,"tdeeMifflin":2662,"tdeeHarris":2754,"maintenance":2662,"weightLoss":2162,"weightGain":3162,"macros":{"protein":200,"carbs":266,"fat":89},"formulas":["Mifflin-St Jeor (1990)","Harris-Benedict revised (1984)"],"note":"Consult a registered dietitian for personalized nutrition advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/running-pace-calculator":{"post":{"operationId":"running_pace_calculator","summary":"Running Pace Calculator","description":"Pace, time or distance from the other two, with splits. Give any two of the three and it returns the missing one, the pace in both minutes per kilometre and per mile, the speed in km/h, a cumulative split for every whole unit, and the finish time at that same pace for seven race distances from 1 km to the marathon.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"solve":{"type":"string"},"unit":{"type":"string"},"distance":{"type":"integer"},"time":{"type":"string"},"pace":{"type":"string"}}},"example":{"solve":"pace","unit":"km","distance":10,"time":"55:00","pace":"5:30"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"solve":{"type":"string"},"unit":{"type":"string"},"distance":{"type":"integer"},"timeSeconds":{"type":"integer"},"time":{"type":"string"},"paceSeconds":{"type":"integer"},"pace":{"type":"string"},"pacePerKm":{"type":"string"},"pacePerMile":{"type":"string"},"speedKmh":{"type":"number"},"resultLabel":{"type":"string"},"resultValue":{"type":"string"},"splits":{"type":"array","items":{"type":"object","properties":{"at":{"type":"integer"},"time":{"type":"string"}}}},"predictions":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"km":{"type":"number"},"time":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"solve":"pace","unit":"km","distance":10,"timeSeconds":3300,"time":"55:00","paceSeconds":330,"pace":"5:30","pacePerKm":"5:30","pacePerMile":"8:51","speedKmh":10.909,"resultLabel":"Pace per km","resultValue":"5:30 /km","splits":[{"at":1,"time":"5:30"}],"predictions":[{"label":"Marathon","km":42.195,"time":"3:52:04"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/tdee-calculator":{"post":{"operationId":"tdee_calculator","summary":"TDEE Calculator","description":"Total daily energy expenditure from BMR and activity level. The basal rate is Mifflin-St Jeor (1990) and comes from the same function the BMR tool uses, multiplied by an activity factor from 1.2 to 1.9, with five ready-made calorie targets from a 500 kcal cut to a 500 kcal bulk. This is an indication, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sex":{"type":"string"},"age":{"type":"integer"},"heightCm":{"type":"integer"},"weightKg":{"type":"integer"},"activityFactor":{"type":"number"}}},"example":{"sex":"male","age":30,"heightCm":178,"weightKg":75,"activityFactor":1.55}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"sex":{"type":"string"},"activityFactor":{"type":"number"},"bmr":{"type":"integer"},"tdee":{"type":"integer"},"formula":{"type":"string"},"targets":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"kcal":{"type":"integer"},"note":{"type":"string"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"sex":"male","activityFactor":1.55,"bmr":1718,"tdee":2662,"formula":"Mifflin-St Jeor (1990)","targets":[{"label":"Maintenance","kcal":2662,"note":"Hold current weight"}],"note":"This is an indication, not medical advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/due-date-calculator":{"post":{"operationId":"due_date_calculator","summary":"Due Date Calculator","description":"Estimated due date and current week from the last period or conception date. Naegele's rule adds 280 days to the first day of the last menstrual period, or 266 days when counting from conception, and the result also carries gestational age, trimester and the 12, 20, 28 and 37 week milestone dates. Estimate only, consult your midwife or doctor.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string"},"method":{"type":"string"},"today":{"type":"string"}}},"example":{"date":"2026-01-01","method":"lmp","today":"2026-03-01"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"method":{"type":"string"},"from":{"type":"string"},"lmpDate":{"type":"string"},"dueDate":{"type":"string"},"gestationalAge":{"type":"object","properties":{"weeks":{"type":"integer"},"days":{"type":"integer"},"totalDays":{"type":"integer"}}},"trimester":{"type":"string"},"milestones":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"date":{"type":"string"}}}},"formula":{"type":"string"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"method":"lmp","from":"2026-01-01","lmpDate":"2026-01-01","dueDate":"2026-10-08","gestationalAge":{"weeks":8,"days":3,"totalDays":59},"trimester":"First trimester","milestones":[{"label":"Full term (37 weeks)","date":"2026-09-17"}],"formula":"LMP + 280 days (Naegele's rule)","note":"Estimate only, consult your midwife or doctor."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/ovulation-calculator":{"post":{"operationId":"ovulation_calculator","summary":"Ovulation Calculator","description":"Fertile window and ovulation day from the last period and cycle length. The calendar method places ovulation 14 days before the next period rather than 14 days after the last one, opens the fertile window five days before it and closes it one day after, and projects the same pattern across three cycles. Estimate only, consult your midwife or doctor.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lastPeriod":{"type":"string"},"cycleLength":{"type":"integer"},"today":{"type":"string"}}},"example":{"lastPeriod":"2026-03-01","cycleLength":28,"today":"2026-03-10"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"cycleLength":{"type":"integer"},"cycles":{"type":"array","items":{"type":"object","properties":{"periodStart":{"type":"string"},"ovulation":{"type":"string"},"fertileStart":{"type":"string"},"fertileEnd":{"type":"string"},"nextPeriod":{"type":"string"}}}},"ovulation":{"type":"string"},"fertileWindow":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}}},"nextPeriod":{"type":"string"},"daysUntilOvulation":{"type":"integer"},"daysUntilNextPeriod":{"type":"integer"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"cycleLength":28,"cycles":[{"periodStart":"2026-03-01","ovulation":"2026-03-15","fertileStart":"2026-03-10","fertileEnd":"2026-03-16","nextPeriod":"2026-03-29"}],"ovulation":"2026-03-15","fertileWindow":{"start":"2026-03-10","end":"2026-03-16"},"nextPeriod":"2026-03-29","daysUntilOvulation":5,"daysUntilNextPeriod":19,"note":"Estimate only, consult your midwife or doctor."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/one-rep-max-calculator":{"post":{"operationId":"one_rep_max_calculator","summary":"One Rep Max Calculator","description":"Estimated one repetition maximum from a weight and reps, with percentages. Four formulas run side by side, Epley, Brzycki, Lombardi and O'Conner, and their average drives a training table from 95% down to 50% with the usual rep range for each load. This is an indication for training purposes, not medical advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weight":{"type":"integer"},"reps":{"type":"integer"}}},"example":{"weight":100,"reps":5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"weight":{"type":"integer"},"reps":{"type":"integer"},"epley":{"type":"number"},"brzycki":{"type":"number"},"lombardi":{"type":"number"},"oconner":{"type":"number"},"average":{"type":"number"},"tested":{"type":"boolean"},"formulas":{"type":"array","items":{"type":"string"}},"percentages":{"type":"array","items":{"type":"object","properties":{"percent":{"type":"integer"},"weight":{"type":"number"},"reps":{"type":"string"},"focus":{"type":"string"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"weight":100,"reps":5,"epley":116.67,"brzycki":112.5,"lombardi":117.46,"oconner":112.5,"average":114.78,"tested":false,"formulas":["Epley","Brzycki","Lombardi","O'Conner"],"percentages":[{"percent":85,"weight":97.56,"reps":"5-6","focus":"Strength"}],"note":"This is an indication for training purposes, not medical advice. Never attempt a true max without proper warm-up and a spotter."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/date-calculator":{"post":{"operationId":"date_calculator","summary":"Date Calculator","description":"Add or subtract days, weeks, months or years from a date and get the date you land on, written out with its weekday. The arithmetic is calendar aware and runs in UTC, so leap years and month lengths are handled and the answer never shifts with a time zone.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"unit":{"type":"string"},"operation":{"type":"string"},"startDate":{"type":"string"}}},"example":{"amount":90,"unit":"days","operation":"add","startDate":"2026-03-15"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"startDate":{"type":"string"},"startWeekday":{"type":"string"},"operation":{"type":"string"},"amount":{"type":"integer"},"unit":{"type":"string"},"resultDate":{"type":"string"},"weekday":{"type":"string"},"formatted":{"type":"string"},"daysDifference":{"type":"integer"}}}}},"example":{"ok":true,"data":{"startDate":"2026-03-15","startWeekday":"Sunday","operation":"add","amount":90,"unit":"days","resultDate":"2026-06-13","weekday":"Saturday","formatted":"Saturday, June 13, 2026","daysDifference":90}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/fraction-calculator":{"post":{"operationId":"fraction_calculator","summary":"Fraction Calculator","description":"Add, subtract, multiply or divide two fractions, mixed numbers included, and get the answer reduced to lowest terms. The arithmetic is exact whole-number arithmetic, so nothing is rounded through a decimal on the way, and the result comes back as a fraction, a mixed number and a decimal.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"a":{"type":"object","properties":{"numerator":{"type":"integer"},"denominator":{"type":"integer"}}},"b":{"type":"object","properties":{"numerator":{"type":"integer"},"denominator":{"type":"integer"}}},"operation":{"type":"string"}}},"example":{"a":{"numerator":1,"denominator":2},"b":{"numerator":1,"denominator":3},"operation":"+"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"numerator":{"type":"integer"},"denominator":{"type":"integer"},"fraction":{"type":"string"},"mixed":{"type":"string"},"decimal":{"type":"number"},"expression":{"type":"string"}}}}},"example":{"ok":true,"data":{"numerator":5,"denominator":6,"fraction":"5/6","mixed":"5/6","decimal":0.8333333333333334,"expression":"1/2 + 1/3 = 5/6"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/ratio-calculator":{"post":{"operationId":"ratio_calculator","summary":"Ratio Calculator","description":"Reduce a ratio to lowest whole-number terms, solve a proportion A:B = C:D for the one value left out, or scale a ratio by a factor for a recipe or a mix. Reducing is exact whole-number arithmetic, so a ratio written with decimals is scaled to integers first rather than rounded.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"a":{"type":"integer"},"b":{"type":"integer"},"c":{"type":"integer"},"d":{"type":"integer"},"factor":{"type":"number"}}},"example":{"mode":"simplify","a":1920,"b":1080,"c":9,"d":12,"factor":1.5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"a":{"type":"integer"},"b":{"type":"integer"},"ratio":{"type":"string"},"decimal":{"type":"number"},"solvedFor":{"type":"null"},"solved":{"type":"null"}}}}},"example":{"ok":true,"data":{"mode":"simplify","a":16,"b":9,"ratio":"16:9","decimal":1.7777777777777777,"solvedFor":null,"solved":null}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/average-calculator":{"post":{"operationId":"average_calculator","summary":"Average Calculator","description":"Every average of a list of numbers in one call: mean, median, mode, sum, count, minimum, maximum, range, and the geometric and harmonic means. A list where nothing repeats has no mode, and the two positive-only means come back null when any value is zero or below.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"values":{"type":"array","items":{"type":"integer"}}}},"example":{"values":[12,15,9,21,15,18]}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"count":{"type":"integer"},"sum":{"type":"integer"},"mean":{"type":"integer"},"median":{"type":"integer"},"modes":{"type":"array","items":{"type":"integer"}},"min":{"type":"integer"},"max":{"type":"integer"},"range":{"type":"integer"},"geometricMean":{"type":"number"},"harmonicMean":{"type":"number"},"sorted":{"type":"array","items":{"type":"integer"}}}}}},"example":{"ok":true,"data":{"count":6,"sum":90,"mean":15,"median":15,"modes":[15],"min":9,"max":21,"range":12,"geometricMean":14.47,"harmonicMean":13.92,"sorted":[9,12,15,15,18,21]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/standard-deviation-calculator":{"post":{"operationId":"standard_deviation_calculator","summary":"Standard Deviation Calculator","description":"Standard deviation and variance of a list of numbers, both the sample and the population figures, plus the mean, the sum and the sum of squared deviations. The `type` field says which of the two the headline standardDeviation reports: the sample one unless asked otherwise, or the population one when there is a single value.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"values":{"type":"array","items":{"type":"integer"}},"type":{"type":"string"}}},"example":{"values":[4,8,15,16,23,42],"type":"sample"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"type":{"type":"string"},"count":{"type":"integer"},"sum":{"type":"integer"},"mean":{"type":"integer"},"sumOfSquares":{"type":"integer"},"sampleStandardDeviation":{"type":"number"},"sampleVariance":{"type":"integer"},"populationStandardDeviation":{"type":"number"},"populationVariance":{"type":"number"},"standardDeviation":{"type":"number"},"variance":{"type":"integer"},"min":{"type":"integer"},"max":{"type":"integer"},"values":{"type":"array","items":{"type":"integer"}}}}}},"example":{"ok":true,"data":{"type":"sample","count":6,"sum":108,"mean":18,"sumOfSquares":910,"sampleStandardDeviation":13.49,"sampleVariance":182,"populationStandardDeviation":12.32,"populationVariance":151.67,"standardDeviation":13.49,"variance":182,"min":4,"max":42,"values":[4,8,15,16,23,42]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/gpa-calculator":{"post":{"operationId":"gpa_calculator","summary":"GPA Calculator","description":"Work out a grade point average from courses that each have a grade and a credit weight, on the 4.0 letter scale, the 10-point European scale or percentages. Returns the GPA on the 4.0 scale with its letter, its honours classification and the credits counted.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"courses":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"grade":{"type":"string"},"credits":{"type":"integer"}}}},"scale":{"type":"string"}}},"example":{"courses":[{"name":"Calculus I","grade":"A","credits":4},{"name":"English Composition","grade":"A-","credits":3},{"name":"Introduction to CS","grade":"B+","credits":3}],"scale":"4.0"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"scale":{"type":"string"},"gpa":{"type":"number"},"letter":{"type":"string"},"classification":{"type":"string"},"totalCredits":{"type":"integer"},"countedCourses":{"type":"integer"},"courses":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"grade":{"type":"string"},"credits":{"type":"integer"},"points":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"scale":"4.0","gpa":3.7,"letter":"A-","classification":"Magna Cum Laude","totalCredits":10,"countedCourses":3,"courses":[{"name":"Calculus I","grade":"A","credits":4,"points":4}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/grade-calculator":{"post":{"operationId":"grade_calculator","summary":"Grade Calculator","description":"Average the assessments of one course into the weighted grade it stands at, from each item's score and what it is worth. Divides by the weight actually entered, so a half-graded course reports the average so far rather than a score dragged down by work not handed in yet.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"integer"},"weight":{"type":"integer"}}}}}},"example":{"items":[{"name":"Homework","score":85,"weight":20},{"name":"Midterm","score":78,"weight":30},{"name":"Project","score":92,"weight":20}]}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"weightedAverage":{"type":"integer"},"totalWeight":{"type":"integer"},"remainingWeight":{"type":"integer"},"countedItems":{"type":"integer"},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"integer"},"weight":{"type":"integer"},"points":{"type":"integer"}}}}}}}},"example":{"ok":true,"data":{"weightedAverage":84,"totalWeight":70,"remainingWeight":30,"countedItems":3,"items":[{"name":"Homework","score":85,"weight":20,"points":1700}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/final-grade-calculator":{"post":{"operationId":"final_grade_calculator","summary":"Final Grade Calculator","description":"Work out the score needed on a final exam to finish a course at a target grade, from the grade so far and what the final is worth. Says plainly whether the target is reachable, already secured, or out of reach, and prices the usual A to D targets at the same weight.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"currentGrade":{"type":"integer"},"targetGrade":{"type":"integer"},"finalWeightPercent":{"type":"integer"}}},"example":{"currentGrade":85,"targetGrade":90,"finalWeightPercent":30}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"currentGrade":{"type":"integer"},"targetGrade":{"type":"integer"},"finalWeightPercent":{"type":"integer"},"requiredScore":{"type":"number"},"achievable":{"type":"boolean"},"alreadySecured":{"type":"boolean"},"verdict":{"type":"object","properties":{"text":{"type":"string"},"kind":{"type":"string"}}},"commonTargets":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"target":{"type":"integer"},"required":{"type":"number"},"verdict":{"type":"object","properties":{"text":{"type":"string"},"kind":{"type":"string"}}}}}}}}}},"example":{"ok":true,"data":{"currentGrade":85,"targetGrade":90,"finalWeightPercent":30,"requiredScore":101.67,"achievable":false,"alreadySecured":false,"verdict":{"text":"Not possible without extra credit","kind":"impossible"},"commonTargets":[{"label":"A (90%)","target":90,"required":101.67,"verdict":{"text":"Not possible without extra credit","kind":"impossible"}}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/test-grade-calculator":{"post":{"operationId":"test_grade_calculator","summary":"Test Grade Calculator","description":"Turn a number of questions and a number of wrong answers into a percentage and a letter grade, with a chart pricing every possible number of wrong answers on that test. The letter cutoffs default to the US ten point scale and can be replaced.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"questions":{"type":"integer"},"wrong":{"type":"integer"},"scale":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"},"c":{"type":"integer"},"d":{"type":"integer"}}}}},"example":{"questions":25,"wrong":3,"scale":{"a":90,"b":80,"c":70,"d":60}}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"questions":{"type":"integer"},"wrong":{"type":"integer"},"correct":{"type":"integer"},"score":{"type":"integer"},"letter":{"type":"string"},"scale":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"},"c":{"type":"integer"},"d":{"type":"integer"}}},"chart":{"type":"array","items":{"type":"object","properties":{"wrong":{"type":"integer"},"score":{"type":"integer"},"letter":{"type":"string"}}}}}}}},"example":{"ok":true,"data":{"questions":25,"wrong":3,"correct":22,"score":88,"letter":"B","scale":{"a":90,"b":80,"c":70,"d":60},"chart":[{"wrong":0,"score":100,"letter":"A"}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/grade-curve-calculator":{"post":{"operationId":"grade_curve_calculator","summary":"Grade Curve Calculator","description":"Curve a set of test scores by one of the three methods teachers use: add the same points to everyone, take the square root and multiply by ten, or scale the highest score in the room up to 100. Returns every adjusted score plus the class mean, median, minimum and maximum before and after.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scores":{"type":"array","items":{"type":"integer"}},"method":{"type":"string"},"flatPoints":{"type":"integer"},"capAt100":{"type":"boolean"}}},"example":{"scores":[72,85,64,91,58,77,69,88],"method":"sqrt","flatPoints":5,"capAt100":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"method":{"type":"string"},"capAt100":{"type":"boolean"},"flatPoints":{"type":"integer"},"count":{"type":"integer"},"scores":{"type":"array","items":{"type":"object","properties":{"original":{"type":"integer"},"curved":{"type":"number"}}}},"before":{"type":"object","properties":{"mean":{"type":"number"},"median":{"type":"number"},"min":{"type":"integer"},"max":{"type":"integer"}}},"after":{"type":"object","properties":{"mean":{"type":"number"},"median":{"type":"number"},"min":{"type":"number"},"max":{"type":"number"}}}}}}},"example":{"ok":true,"data":{"method":"sqrt","capAt100":true,"flatPoints":5,"count":8,"scores":[{"original":72,"curved":84.85}],"before":{"mean":75.5,"median":74.5,"min":58,"max":91},"after":{"mean":86.65,"median":86.3,"min":76.16,"max":95.39}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/percentage-to-cgpa-calculator":{"post":{"operationId":"percentage_to_cgpa_calculator","summary":"Percentage to CGPA Calculator","description":"Convert percentage marks to CGPA on the Indian 10-point scale using the CBSE divisor of 9.5, or convert a CGPA back to a percentage. Returns the indicative American 4-point GPA alongside it, which is what an overseas application asks for.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"integer"},"direction":{"type":"string"},"divisor":{"type":"number"}}},"example":{"value":85,"direction":"toCgpa","divisor":9.5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"direction":{"type":"string"},"divisor":{"type":"number"},"input":{"type":"integer"},"cgpa":{"type":"number"},"percentage":{"type":"integer"},"gpa4":{"type":"number"},"formula":{"type":"string"}}}}},"example":{"ok":true,"data":{"direction":"toCgpa","divisor":9.5,"input":85,"cgpa":8.95,"percentage":85,"gpa4":3.15,"formula":"85 / 9.5 = 8.95 CGPA"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/meeting-cost-calculator":{"post":{"operationId":"meeting_cost_calculator","summary":"Meeting Cost Calculator","description":"Price a meeting in salary from the number of attendees, one blended hourly rate and how long it runs. Returns the total, the cost per minute and the cost per person, so a recurring meeting can be weighed against what it produces.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"attendees":{"type":"integer"},"hourlyRate":{"type":"integer"},"durationMinutes":{"type":"integer"},"currency":{"type":"string"}}},"example":{"attendees":5,"hourlyRate":75,"durationMinutes":60,"currency":"USD"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"attendees":{"type":"integer"},"hourlyRate":{"type":"integer"},"durationMinutes":{"type":"integer"},"currency":{"type":"string"},"symbol":{"type":"string"},"totalCost":{"type":"integer"},"costPerMinute":{"type":"number"},"costPerHour":{"type":"integer"},"costPerPerson":{"type":"integer"},"formattedTotal":{"type":"string"}}}}},"example":{"ok":true,"data":{"attendees":5,"hourlyRate":75,"durationMinutes":60,"currency":"USD","symbol":"$","totalCost":375,"costPerMinute":6.25,"costPerHour":375,"costPerPerson":75,"formattedTotal":"$375.00"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/fuel-cost-calculator":{"post":{"operationId":"fuel_cost_calculator","summary":"Fuel Cost Calculator","description":"Work out the fuel a journey burns and what it costs, from a distance in kilometres or miles, a consumption figure in any of the four common units, and a pump price per litre or gallon. Handles return trips, a repeat count for a monthly commute, and splitting the bill between passengers.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"distance":{"type":"integer"},"distanceUnit":{"type":"string"},"consumption":{"type":"number"},"consumptionUnit":{"type":"string"},"price":{"type":"number"},"priceUnit":{"type":"string"},"passengers":{"type":"integer"},"roundTrip":{"type":"boolean"},"trips":{"type":"integer"}}},"example":{"distance":100,"distanceUnit":"km","consumption":6.5,"consumptionUnit":"l100km","price":1.95,"priceUnit":"liter","passengers":1,"roundTrip":false,"trips":1}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"totalKm":{"type":"integer"},"liters":{"type":"number"},"cost":{"type":"number"},"costPerPerson":{"type":"number"},"passengers":{"type":"integer"},"trips":{"type":"integer"},"roundTrip":{"type":"boolean"},"litersPer100Km":{"type":"number"},"pricePerLiter":{"type":"number"},"costPerTrip":{"type":"number"}}}}},"example":{"ok":true,"data":{"totalKm":100,"liters":6.5,"cost":12.68,"costPerPerson":12.68,"passengers":1,"trips":1,"roundTrip":false,"litersPer100Km":6.5,"pricePerLiter":1.95,"costPerTrip":12.68}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/appliance-energy-cost-calculator":{"post":{"operationId":"appliance_energy_cost_calculator","summary":"Appliance Energy Cost Calculator","description":"Price what an appliance costs to run, from its wattage, how it is used and the price of a kilowatt hour. Takes either a daily pattern (hours a day) or a weekly one (runs per week times minutes a run), and returns kilowatt hours and money per day, per month and per year.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"watts":{"type":"integer"},"pricePerKwh":{"type":"number"},"mode":{"type":"string"},"hoursPerDay":{"type":"integer"},"usesPerWeek":{"type":"integer"},"minutesPerUse":{"type":"integer"}}},"example":{"watts":90,"pricePerKwh":0.3,"mode":"daily","hoursPerDay":4,"usesPerWeek":4,"minutesPerUse":60}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"watts":{"type":"integer"},"mode":{"type":"string"},"pricePerKwh":{"type":"number"},"kwhDay":{"type":"number"},"kwhMonth":{"type":"number"},"kwhYear":{"type":"number"},"costDay":{"type":"number"},"costMonth":{"type":"number"},"costYear":{"type":"number"},"hoursPerDay":{"type":"integer"}}}}},"example":{"ok":true,"data":{"watts":90,"mode":"daily","pricePerKwh":0.3,"kwhDay":0.36,"kwhMonth":10.96,"kwhYear":131.49,"costDay":0.11,"costMonth":3.29,"costYear":39.45,"hoursPerDay":4}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dog-age-calculator":{"post":{"operationId":"dog_age_calculator","summary":"Dog Age Calculator","description":"Convert a dog's age into human years with the epigenetic research formula, 16 x ln(age) + 31, and return the size-adjusted chart estimate and the old multiply-by-seven answer next to it. Cats are handled by the standard veterinary chart.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"age":{"type":"integer"},"species":{"type":"string"},"size":{"type":"string"}}},"example":{"age":5,"species":"dog","size":"medium"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"species":{"type":"string"},"size":{"type":"string"},"age":{"type":"integer"},"humanYears":{"type":"number"},"method":{"type":"string"},"chartYears":{"type":"integer"},"sevenTimesRule":{"type":"integer"}}}}},"example":{"ok":true,"data":{"species":"dog","size":"medium","age":5,"humanYears":56.75,"method":"Modern research formula","chartYears":36,"sevenTimesRule":35}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/aspect-ratio-calculator":{"post":{"operationId":"aspect_ratio_calculator","summary":"Aspect Ratio Calculator","description":"Reduce a width and a height to their aspect ratio, or work out the side that is missing at a given ratio so an image or a canvas keeps its proportions. Also returns the decimal ratio, the megapixels and whether the result is landscape, portrait or square.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"width":{"type":"integer"},"height":{"type":"integer"},"ratioWidth":{"type":"integer"},"ratioHeight":{"type":"integer"}}},"example":{"width":1920,"height":1080,"ratioWidth":16,"ratioHeight":9}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"width":{"type":"integer"},"height":{"type":"integer"},"ratio":{"type":"string"},"ratioWidth":{"type":"integer"},"ratioHeight":{"type":"integer"},"decimal":{"type":"number"},"megapixels":{"type":"number"},"orientation":{"type":"string"},"solvedFor":{"type":"string"}}}}},"example":{"ok":true,"data":{"width":1920,"height":1080,"ratio":"16:9","ratioWidth":16,"ratioHeight":9,"decimal":1.7777777777777777,"megapixels":2.0736,"orientation":"landscape","solvedFor":"ratio"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/military-time-converter":{"post":{"operationId":"military_time_converter","summary":"Military Time Converter","description":"Convert a time between the 24-hour military clock and the 12-hour AM/PM clock, in either direction, and get both spellings plus the spoken military phrase. Accepts 1:30 PM, 130pm, 1330 and 13:00.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"time":{"type":"string"},"from":{"type":"string"}}},"example":{"time":"1:30 PM","from":"auto"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"input":{"type":"string"},"readAs":{"type":"string"},"hours":{"type":"integer"},"minutes":{"type":"integer"},"military":{"type":"string"},"military24":{"type":"string"},"standard":{"type":"string"},"spoken":{"type":"string"}}}}},"example":{"ok":true,"data":{"input":"1:30 PM","readAs":"standard","hours":13,"minutes":30,"military":"1330","military24":"13:30","standard":"1:30 PM","spoken":"13 30 (1330 hours)"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/timesheet-calculator":{"post":{"operationId":"timesheet_calculator","summary":"Timesheet Calculator","description":"Turn clock-in and clock-out times into hours worked: the net hours per day after the break, the weekly total in decimal hours and in hours and minutes, the split between regular and overtime hours at a threshold you choose, and gross pay at a flat hourly rate. Overnight shifts are handled, a day nobody worked is reported rather than guessed, and the result carries the page's caveat that this is an indication, not payroll or legal advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"},"breakMinutes":{"type":"integer"}}}},"overtimeThresholdHours":{"type":"integer"},"hourlyRate":{"type":"integer"}}},"example":{"days":[{"label":"Monday","start":"09:00","end":"17:30","breakMinutes":30},{"label":"Tuesday","start":"09:00","end":"17:30","breakMinutes":30},{"label":"Wednesday","start":"09:00","end":"17:30","breakMinutes":30},{"label":"Thursday","start":"09:00","end":"17:30","breakMinutes":30},{"label":"Friday","start":"09:00","end":"17:00","breakMinutes":30}],"overtimeThresholdHours":40,"hourlyRate":25}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"days":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"},"breakMinutes":{"type":"integer"},"hours":{"type":"integer"},"overnight":{"type":"boolean"}}}},"totalHours":{"type":"number"},"totalHoursHm":{"type":"string"},"regularHours":{"type":"number"},"overtimeHours":{"type":"integer"},"overtimeThresholdHours":{"type":"integer"},"daysWorked":{"type":"integer"},"hourlyRate":{"type":"integer"},"grossPay":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"days":[{"label":"Monday","start":"09:00","end":"17:30","breakMinutes":30,"hours":8,"overnight":false}],"totalHours":39.5,"totalHoursHm":"39h 30m","regularHours":39.5,"overtimeHours":0,"overtimeThresholdHours":40,"daysWorked":5,"hourlyRate":25,"grossPay":987.5,"note":"Indication only, not payroll or legal advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/overtime-calculator":{"post":{"operationId":"overtime_calculator","summary":"Overtime Calculator","description":"Work out a pay packet that mixes rates: regular hours at the base rate, overtime hours at a multiplier such as the 1.5x the United States FLSA sets as a minimum, and double-time hours at twice the base rate. Returns each line, the total, the total hours and the effective rate across all of them, which is the number to compare a salaried offer against. An indication, not payroll or legal advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hourlyRate":{"type":"integer"},"regularHours":{"type":"integer"},"overtimeHours":{"type":"integer"},"doubleTimeHours":{"type":"integer"},"overtimeMultiplier":{"type":"number"}}},"example":{"hourlyRate":25,"regularHours":40,"overtimeHours":5,"doubleTimeHours":0,"overtimeMultiplier":1.5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"hourlyRate":{"type":"integer"},"overtimeMultiplier":{"type":"number"},"overtimeRate":{"type":"number"},"doubleTimeRate":{"type":"integer"},"regularPay":{"type":"integer"},"overtimePay":{"type":"number"},"doubleTimePay":{"type":"integer"},"totalPay":{"type":"number"},"totalHours":{"type":"integer"},"effectiveRate":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"hourlyRate":25,"overtimeMultiplier":1.5,"overtimeRate":37.5,"doubleTimeRate":50,"regularPay":1000,"overtimePay":187.5,"doubleTimePay":0,"totalPay":1187.5,"totalHours":45,"effectiveRate":26.39,"note":"Indication only, not payroll or legal advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/pto-calculator":{"post":{"operationId":"pto_calculator","summary":"PTO Calculator","description":"Project a paid time off balance: give an accrual rate either as hours per pay period or as days per year, plus the hours in the bank and the hours already booked, and get the accrual per year and per month, the balance after what is planned, twelve months of projected balance, and the balance on a target date. Accrual caps, carry-over deadlines and waiting periods are deliberately not modelled, which the note repeats.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"},"accrualPerPeriod":{"type":"number"},"payFrequency":{"type":"string"},"daysPerYear":{"type":"integer"},"hoursPerDay":{"type":"integer"},"currentBalanceHours":{"type":"integer"},"plannedHours":{"type":"integer"},"targetDate":{"type":"string"},"today":{"type":"string"}}},"example":{"mode":"perPeriod","accrualPerPeriod":4.62,"payFrequency":"biweekly","daysPerYear":15,"hoursPerDay":8,"currentBalanceHours":40,"plannedHours":0,"targetDate":"2026-07-01","today":"2026-01-01"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"hoursPerYear":{"type":"number"},"daysPerYear":{"type":"number"},"hoursPerMonth":{"type":"number"},"hoursPerDay":{"type":"integer"},"balanceAfterPlanned":{"type":"integer"},"daysAfterPlanned":{"type":"integer"},"projected":{"type":"object","properties":{"date":{"type":"string"},"hours":{"type":"number"},"days":{"type":"number"},"months":{"type":"number"}}},"schedule":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"hours":{"type":"integer"},"days":{"type":"number"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"hoursPerYear":120.12,"daysPerYear":15.02,"hoursPerMonth":10.01,"hoursPerDay":8,"balanceAfterPlanned":40,"daysAfterPlanned":5,"projected":{"date":"2026-07-01","hours":99.5,"days":12.4,"months":5.9},"schedule":[{"month":"Feb 2026","hours":50,"days":6.3}],"note":"Indication only. Accrual caps, carry-over limits, and waiting periods in your company policy are not modeled."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/salary-calculator-converter":{"post":{"operationId":"salary_calculator_converter","summary":"Salary Converter","description":"Express one gross salary in every pay period at once: per hour, per day, per week, per fortnight, per month and per year. Give the amount and the period it is quoted in, and adjust the hours per week and weeks per year for part-time, term-time or shift work. A day is a fifth of the working week rather than a fixed eight hours, so a 36 hour week converts honestly. Gross only: tax, contributions and benefits are not modelled.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"period":{"type":"string"},"hoursPerWeek":{"type":"integer"},"weeksPerYear":{"type":"integer"}}},"example":{"amount":85000,"period":"annual","hoursPerWeek":40,"weeksPerYear":52}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"period":{"type":"string"},"amount":{"type":"integer"},"hoursPerWeek":{"type":"integer"},"weeksPerYear":{"type":"integer"},"hoursPerYear":{"type":"integer"},"annual":{"type":"integer"},"monthly":{"type":"number"},"biweekly":{"type":"number"},"weekly":{"type":"number"},"daily":{"type":"number"},"hourly":{"type":"number"}}}}},"example":{"ok":true,"data":{"period":"annual","amount":85000,"hoursPerWeek":40,"weeksPerYear":52,"hoursPerYear":2080,"annual":85000,"monthly":7083.33,"biweekly":3269.23,"weekly":1634.62,"daily":326.92,"hourly":40.87}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/hypotheek-calculator":{"post":{"operationId":"hypotheek_calculator","summary":"Dutch Mortgage Calculator","description":"Work out what a Dutch mortgage costs per month: an annuiteitenhypotheek, where the payment stays the same and the split between interest and repayment shifts, or a lineaire hypotheek, where the payment falls every month. Returns the first and last monthly payment, the interest in the first month, the total interest and the total cost, plus a net payment after hypotheekrenteaftrek when you give the deduction rate. An indication, not mortgage or tax advice: NHG, income tests and lender conditions are not modelled.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"years":{"type":"integer"},"annualRatePercent":{"type":"number"},"type":{"type":"string"},"deductionRatePercent":{"type":"number"}}},"example":{"amount":300000,"years":30,"annualRatePercent":4.5,"type":"annuiteit","deductionRatePercent":36.97}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"amount":{"type":"integer"},"years":{"type":"integer"},"months":{"type":"integer"},"annualRatePercent":{"type":"number"},"type":{"type":"string"},"firstMonthlyPayment":{"type":"number"},"lastMonthlyPayment":{"type":"number"},"firstMonthInterest":{"type":"integer"},"totalInterest":{"type":"number"},"totalCost":{"type":"number"},"deductionRatePercent":{"type":"number"},"netFirstMonthlyPayment":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"amount":300000,"years":30,"months":360,"annualRatePercent":4.5,"type":"annuiteit","firstMonthlyPayment":1520.06,"lastMonthlyPayment":1520.06,"firstMonthInterest":1125,"totalInterest":247220.13,"totalCost":547220.13,"deductionRatePercent":36.97,"netFirstMonthlyPayment":1104.15,"note":"This is an indication, not mortgage or tax advice."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/salaris-calculator":{"post":{"operationId":"salaris_calculator","summary":"Dutch Salary Calculator","description":"Estimate a Dutch net salary from a gross one, or the gross salary needed for a net one, with the 2026 box 1 brackets (36.97% up to EUR 75,518, 49.50% above) and the two credits that follow income: the algemene heffingskorting and the arbeidskorting. Returns gross and net per year and per month, the tax before and after credits, each credit, and the effective rate. An estimate based on 2026 Dutch tax rates: it does not account for social security premiums, pension contributions, or individual circumstances, so it is not tax advice.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer"},"period":{"type":"string"},"mode":{"type":"string"}}},"example":{"amount":45000,"period":"year","mode":"grossToNet"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"mode":{"type":"string"},"grossAnnual":{"type":"integer"},"grossMonthly":{"type":"integer"},"taxBeforeCredits":{"type":"number"},"generalTaxCredit":{"type":"number"},"employmentTaxCredit":{"type":"number"},"taxAfterCredits":{"type":"number"},"netAnnual":{"type":"number"},"netMonthly":{"type":"number"},"effectiveTaxRatePercent":{"type":"number"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"mode":"grossToNet","grossAnnual":45000,"grossMonthly":3750,"taxBeforeCredits":16636.5,"generalTaxCredit":2023.5,"employmentTaxCredit":5203.77,"taxAfterCredits":9409.24,"netAnnual":35590.76,"netMonthly":2965.9,"effectiveTaxRatePercent":20.91,"note":"This calculation is an estimate based on 2026 Dutch tax rates."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-student-finance-calculator":{"post":{"operationId":"dutch_student_finance_calculator","summary":"Dutch Student Finance Calculator","description":"Estimate what DUO pays a Dutch student each month: the basisbeurs for mbo or hbo/wo, living at home or away, plus the aanvullende beurs that follows the parents' income, and the student travel product. The middle income band returns a range rather than a number, because DUO phases the aanvullende beurs out between roughly EUR 41,500 and EUR 70,000 of combined 2024 parental income. An indication, not advice: the official 2026 DUO amounts are indexed during the year.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"study":{"type":"string"},"living":{"type":"string"},"parentalIncome":{"type":"string"}}},"example":{"study":"hbowo","living":"away","parentalIncome":"low"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"study":{"type":"string"},"living":{"type":"string"},"parentalIncome":{"type":"string"},"basicGrant":{"type":"number"},"supplementaryGrantLow":{"type":"number"},"supplementaryGrantHigh":{"type":"number"},"totalLow":{"type":"number"},"totalHigh":{"type":"number"},"travelProduct":{"type":"string"},"supplementaryNote":{"type":"string"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"study":"hbowo","living":"away","parentalIncome":"low","basicGrant":324.52,"supplementaryGrantLow":491.08,"supplementaryGrantHigh":491.08,"totalLow":815.6,"totalHigh":815.6,"travelProduct":"Free public transport on weekdays or weekends, by choice.","supplementaryNote":"With a combined parental income below roughly EUR 41,500 you typically get the full aanvullende beurs.","note":"This is an indication, not advice. Check duo.nl for your personal calculation."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/dutch-exam-grade-calculator":{"post":{"operationId":"dutch_exam_grade_calculator","summary":"Dutch Exam Grade Calculator","description":"Turn Dutch exam results into final grades and a havo/vwo pass check: per subject the eindcijfer is the average of the schoolexamen and centraal examen grade rounded to a whole number, and the slaag-zakregeling is then applied in full, the CE average of 5.50, the kernvakkenregel for Nederlands, Engels and wiskunde, and the compensation rules. A CE grade may be given directly or derived from the points scored, the maximum score and the N-term. An indication, not an official determination, and vmbo has its own regulation.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subjects":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"se":{"type":"number"},"ce":{"type":"number"}}}}}},"example":{"subjects":[{"name":"Nederlands","se":6.5,"ce":6.2},{"name":"Engels","se":7.1,"ce":6.8},{"name":"Wiskunde A","se":5.8,"ce":5.4},{"name":"Geschiedenis","se":6.9,"ce":7.2},{"name":"Biologie","se":6.4,"ce":6.1},{"name":"Economie","se":7,"ce":6.6}]}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"subjects":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"se":{"type":"number"},"ce":{"type":"number"},"hasCe":{"type":"boolean"},"final":{"type":"integer"},"core":{"type":"boolean"}}}},"subjectCount":{"type":"integer"},"ceAverage":{"type":"number"},"finalAverage":{"type":"number"},"passed":{"type":"boolean"},"checks":{"type":"array","items":{"type":"object","properties":{"ok":{"type":"boolean"},"text":{"type":"string"}}}},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"subjects":[{"name":"Nederlands","se":6.5,"ce":6.2,"hasCe":true,"final":6,"core":true}],"subjectCount":6,"ceAverage":6.38,"finalAverage":6.5,"passed":true,"checks":[{"ok":true,"text":"All final grades are 6 or higher"}],"note":"This is an indication, not advice. Always check examenblad.nl."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/credit-card-validator":{"post":{"operationId":"credit_card_validator","summary":"Card Number Validator","description":"Check a payment card number the way a checkout form does: the Luhn checksum that every major network uses to catch a mistyped digit, and the scheme behind the prefix (Visa, Mastercard, Amex, Discover, JCB, Diners). It is a format check and nothing more: it says nothing about whether a card exists, is active or has funds. The number is never logged, stored or returned, and the result shows at most the last four digits.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cardNumber":{"type":"string"}}},"example":{"cardNumber":"4242 4242 4242 4242"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"luhnValid":{"type":"boolean"},"scheme":{"type":"string"},"length":{"type":"integer"},"masked":{"type":"string"},"note":{"type":"string"}}}}},"example":{"ok":true,"data":{"valid":true,"luhnValid":true,"scheme":"Visa","length":16,"masked":"**** **** **** 4242","note":"Format check only. Nothing is logged or stored."}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/add-line-numbers":{"post":{"operationId":"add_line_numbers","summary":"Add Line Numbers","description":"Put a sequential number in front of every line of a text, with a chosen first number, step and separator. Blank lines can keep their place without taking a number, and the numbers can be padded so the text after them lines up. Use it to cite log lines, contract clauses or manuscript lines.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"start":{"type":"integer"},"step":{"type":"integer"},"separator":{"type":"string"},"skipEmptyLines":{"type":"boolean"},"padNumbers":{"type":"boolean"}}},"example":{"text":"alpha\nbeta\ngamma","start":1,"step":1,"separator":". ","skipEmptyLines":false,"padNumbers":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"lines":{"type":"integer"},"numbered":{"type":"integer"},"lastNumber":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"1. alpha\n2. beta\n3. gamma","lines":3,"numbered":3,"lastNumber":3}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/add-prefix-suffix":{"post":{"operationId":"add_prefix_suffix","summary":"Add Prefix and Suffix","description":"Put the same text in front of and behind every line of a text. Nothing is escaped or interpreted, so quotes, commas, brackets and HTML tags all pass through as typed. Use it to wrap a column of values in quotes for a SQL IN clause, turn a keyword list into hashtags, or build list markup from plain lines.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"prefix":{"type":"string"},"suffix":{"type":"string"},"skipEmptyLines":{"type":"boolean"}}},"example":{"text":"apple\nbanana\ncherry","prefix":"'","suffix":"',","skipEmptyLines":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"lines":{"type":"integer"},"changed":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"'apple',\n'banana',\n'cherry',","lines":3,"changed":3}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/remove-line-breaks":{"post":{"operationId":"remove_line_breaks","summary":"Remove Line Breaks","description":"Join lines that were broken in the wrong places back into flowing text. Each line is trimmed and blank lines are dropped before the join, and the separator can be a space, nothing, or any string such as a comma. Keeping paragraph breaks treats a blank line as a boundary, so a document keeps its shape instead of becoming one wall of text.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"separator":{"type":"string"},"keepParagraphs":{"type":"boolean"}}},"example":{"text":"A sentence that\nwas broken\nin three.\n\nA second paragraph.","separator":" ","keepParagraphs":true}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"linesJoined":{"type":"integer"},"paragraphs":{"type":"integer"},"breaksRemoved":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"A sentence that was broken in three.\n\nA second paragraph.","linesJoined":4,"paragraphs":2,"breaksRemoved":2}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/tsv-to-csv":{"post":{"operationId":"tsv_to_csv","summary":"TSV to CSV","description":"Convert tab separated text to CSV, or CSV back to TSV, quoting every field that needs it. A field holding the output delimiter, a double quote, a line break or outer whitespace is wrapped in quotes with inner quotes doubled, which is what stops a value such as Hex bolt, stainless from silently gaining a column. Semicolon output and a UTF-8 byte order mark are there for Excel in comma-decimal locales.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"direction":{"type":"string"},"delimiter":{"type":"string"},"crlf":{"type":"boolean"},"byteOrderMark":{"type":"boolean"},"trailingNewline":{"type":"boolean"}}},"example":{"text":"product\tdescription\tprice\nAB-12\tHex bolt, stainless\t0,45","direction":"tsv-to-csv","delimiter":",","crlf":false,"byteOrderMark":false,"trailingNewline":false}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"rows":{"type":"integer"},"columns":{"type":"integer"},"quotedFields":{"type":"integer"},"raggedLines":{"type":"array","items":{}},"delimiter":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"product,description,price\nAB-12,\"Hex bolt, stainless\",\"0,45\"","rows":2,"columns":3,"quotedFields":2,"raggedLines":[],"delimiter":","}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/csv-column-extractor":{"post":{"operationId":"csv_column_extractor","summary":"CSV Column Extractor","description":"Keep, drop or reorder the columns of a CSV. Columns are named by header text or by 1-based position, and the order they are asked for is the order they come back in, so the same call also reorders. The delimiter is detected when not given, quoted fields holding commas and line breaks survive, and the result is either CSV or one value per line.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"},"columns":{"type":"array","items":{"type":"string"}},"exclude":{"type":"boolean"},"delimiter":{"type":"string"},"firstRowHeader":{"type":"boolean"},"format":{"type":"string"}}},"example":{"csv":"name,email,city\nAlice,alice@example.com,Amsterdam\nBob,bob@example.com,Utrecht","columns":["city","name"],"exclude":false,"delimiter":"auto","firstRowHeader":true,"format":"csv"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"headers":{"type":"array","items":{"type":"string"}},"columns":{"type":"integer"},"rows":{"type":"integer"},"delimiter":{"type":"string"}}}}},"example":{"ok":true,"data":{"result":"city,name\nAmsterdam,Alice\nUtrecht,Bob","headers":["city","name"],"columns":2,"rows":2,"delimiter":","}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/character-counter":{"post":{"operationId":"character_counter","summary":"Character Counter","description":"Count the characters, characters without spaces, words, sentences, lines and paragraphs of a text, and rank how often each character appears. The shared counts are defined exactly as the word counter defines them, so the two never disagree about the same paragraph. Use it against a field limit: 280 for a post, 160 for an SMS, 155 for a meta description.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"limit":{"type":"integer"}}},"example":{"text":"Hello world. This is a test!","limit":5}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"characters":{"type":"integer"},"charactersNoSpaces":{"type":"integer"},"words":{"type":"integer"},"sentences":{"type":"integer"},"lines":{"type":"integer"},"paragraphs":{"type":"integer"},"readingTime":{"type":"object","properties":{"minutes":{"type":"integer"},"seconds":{"type":"integer"},"text":{"type":"string"}}},"frequencies":{"type":"array","items":{"type":"object","properties":{"character":{"type":"string"},"count":{"type":"integer"},"percent":{"type":"number"}}}}}}}},"example":{"ok":true,"data":{"characters":28,"charactersNoSpaces":23,"words":6,"sentences":2,"lines":1,"paragraphs":1,"readingTime":{"minutes":0,"seconds":2,"text":"2 sec"},"frequencies":[{"character":"l","count":3,"percent":13.04}]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/text-to-binary":{"post":{"operationId":"text_to_binary","summary":"Text to Binary","description":"Turn text into its UTF-8 bytes written as binary, hexadecimal, octal and decimal, or read those numbers back into text. The unit is the byte, not the character, so an accented letter is two groups and an emoji four. Both directions return all four bases at once, and decoding rejects a value that is not a byte or a byte sequence that is not valid UTF-8.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input":{"type":"string"},"action":{"type":"string"},"format":{"type":"string"},"separator":{"type":"string"}}},"example":{"input":"Hi","action":"encode","format":"binary","separator":"space"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"text":{"type":"string"},"binary":{"type":"string"},"hex":{"type":"string"},"octal":{"type":"string"},"decimal":{"type":"string"},"characters":{"type":"integer"},"bytes":{"type":"integer"},"bits":{"type":"integer"}}}}},"example":{"ok":true,"data":{"text":"Hi","binary":"01001000 01101001","hex":"48 69","octal":"110 151","decimal":"72 105","characters":2,"bytes":2,"bits":16}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/upside-down-text":{"post":{"operationId":"upside_down_text","summary":"Upside Down Text","description":"Flip text upside down, or mirror it left to right, using Unicode characters that look like rotated or reflected Latin letters. The string is reversed after mapping, so it reads correctly when the screen is turned round or held up to a mirror. The result is ordinary text, so it can be pasted into a bio, a username or a chat message.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"mode":{"type":"string"}}},"example":{"text":"hello world","mode":"upside"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"mode":{"type":"string"},"mapped":{"type":"integer"},"unchanged":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"plɹoʍ ollǝɥ","mode":"upside","mapped":10,"unchanged":1}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/leetspeak-translator":{"post":{"operationId":"leetspeak_translator","summary":"Leetspeak Translator","description":"Write text in leetspeak at one of three intensities, or read leetspeak back into plain text. Basic swaps the six canonical letters (a4 e3 i1 o0 s5 t7), advanced adds four more, and extreme builds most of the alphabet out of punctuation. Decoding is best effort because leet is ambiguous: the digit 1 stands for both i and l, and this resolves it to l.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"action":{"type":"string"},"intensity":{"type":"string"}}},"example":{"text":"paste your leet here","action":"encode","intensity":"basic"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"result":{"type":"string"},"action":{"type":"string"},"intensity":{"type":"string"},"substitutions":{"type":"integer"}}}}},"example":{"ok":true,"data":{"result":"p4573 y0ur l337 h3r3","action":"encode","intensity":"basic","substitutions":10}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/keyword-density-analyzer":{"post":{"operationId":"keyword_density_analyzer","summary":"Keyword Density Analyzer","description":"Count how often each word or phrase appears in a text and what share of it they take up. Phrases are one to five words long, stop words are left out of the table but still counted in the total, and one target keyword can be reported separately whatever its count. Each row is banded: under 1 percent is sparse, 1 to 3 is good, over 5 reads as stuffing.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"phraseLength":{"type":"integer"},"minCount":{"type":"integer"},"limit":{"type":"integer"},"excludeStopWords":{"type":"boolean"},"sortBy":{"type":"string"},"order":{"type":"string"},"targetKeyword":{"type":"string"}}},"example":{"text":"Keyword density matters. Keyword density is the share of a page a keyword takes up. Good keyword density is between one and three percent.","phraseLength":2,"minCount":2,"limit":10,"excludeStopWords":true,"sortBy":"count","order":"desc","targetKeyword":"keyword density"}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"totalWords":{"type":"integer"},"characters":{"type":"integer"},"uniqueKeywords":{"type":"integer"},"averageWordLength":{"type":"number"},"keywords":{"type":"array","items":{"type":"object","properties":{"keyword":{"type":"string"},"count":{"type":"integer"},"density":{"type":"number"},"band":{"type":"string"}}}},"target":{"type":"object","properties":{"keyword":{"type":"string"},"count":{"type":"integer"},"density":{"type":"number"},"band":{"type":"string"}}}}}}},"example":{"ok":true,"data":{"totalWords":24,"characters":138,"uniqueKeywords":2,"averageWordLength":4.67,"keywords":[{"keyword":"keyword density","count":3,"density":12.5,"band":"overstuffed"}],"target":{"keyword":"keyword density","count":3,"density":12.5,"band":"overstuffed"}}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/readability-checker":{"post":{"operationId":"readability_checker","summary":"Readability Checker","description":"Score how hard a text is to read with six named formulas: Flesch Reading Ease, Flesch-Kincaid Grade, Gunning Fog, SMOG, Coleman-Liau and the Automated Readability Index. Each is returned under its own name alongside the word, sentence and syllable counts it was built from, plus the mean grade, the school year it maps to and the audience that suits.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}}},"example":{"text":"The quick brown fox jumps over the lazy dog. This is a sample text to demonstrate the readability checker tool."}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"words":{"type":"integer"},"sentences":{"type":"integer"},"syllables":{"type":"integer"},"complexWords":{"type":"integer"},"letters":{"type":"integer"},"averageWordsPerSentence":{"type":"integer"},"averageSyllablesPerWord":{"type":"number"},"scores":{"type":"object","properties":{"fleschReadingEase":{"type":"number"},"fleschKincaidGrade":{"type":"number"},"gunningFog":{"type":"integer"},"smogIndex":{"type":"number"},"colemanLiauIndex":{"type":"number"},"automatedReadabilityIndex":{"type":"number"}}},"averageGrade":{"type":"number"},"gradeLabel":{"type":"string"},"audience":{"type":"string"},"fleschLabel":{"type":"string"}}}}},"example":{"ok":true,"data":{"words":20,"sentences":2,"syllables":31,"complexWords":3,"letters":90,"averageWordsPerSentence":10,"averageSyllablesPerWord":1.55,"scores":{"fleschReadingEase":65.56,"fleschKincaidGrade":6.6,"gunningFog":10,"smogIndex":9.71,"colemanLiauIndex":7.7,"automatedReadabilityIndex":4.77},"averageGrade":7.75,"gradeLabel":"8th grade","audience":"Middle school","fleschLabel":"Standard"}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/tools/text-similarity-analyzer":{"post":{"operationId":"text_similarity_analyzer","summary":"Text Similarity Analyzer","description":"Compare two texts with five named measures instead of one unexplained percentage: Jaccard over the word sets, cosine over the word counts, word overlap, Levenshtein edit distance with a ratio, and the longest common subsequence of words. It also returns the words the two share and the words only one of them has, plus word, character and sentence counts for each.","tags":["tools"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"textA":{"type":"string"},"textB":{"type":"string"},"commonWordLimit":{"type":"integer"},"uniqueWordLimit":{"type":"integer"}}},"example":{"textA":"The quick brown fox jumps over the lazy dog.","textB":"The quick brown cat jumps over the lazy dog.","commonWordLimit":50,"uniqueWordLimit":30}}}},"responses":{"200":{"description":"Successful result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"overall":{"type":"number"},"jaccard":{"type":"number"},"cosine":{"type":"number"},"wordOverlap":{"type":"number"},"levenshteinDistance":{"type":"integer"},"levenshteinRatio":{"type":"number"},"levenshteinTruncated":{"type":"boolean"},"longestCommonSubsequence":{"type":"integer"},"a":{"type":"object","properties":{"words":{"type":"integer"},"characters":{"type":"integer"},"sentences":{"type":"integer"}}},"b":{"type":"object","properties":{"words":{"type":"integer"},"characters":{"type":"integer"},"sentences":{"type":"integer"}}},"commonWords":{"type":"array","items":{"type":"string"}},"onlyInA":{"type":"array","items":{"type":"string"}},"onlyInB":{"type":"array","items":{"type":"string"}}}}}},"example":{"ok":true,"data":{"overall":0.84,"jaccard":0.78,"cosine":0.91,"wordOverlap":0.78,"levenshteinDistance":3,"levenshteinRatio":0.93,"levenshteinTruncated":false,"longestCommonSubsequence":8,"a":{"words":9,"characters":44,"sentences":1},"b":{"words":9,"characters":44,"sentences":1},"commonWords":["the","quick","brown","jumps","over","lazy","dog"],"onlyInA":["fox"],"onlyInB":["cat"]}}}}},"400":{"description":"Missing or invalid request fields"},"401":{"description":"Invalid or missing API key"},"429":{"description":"Monthly plan limit reached"}}}},"/api/v1/ai/scam-message-check":{"post":{"operationId":"ai_check_scam_message","summary":"Scam message checker","description":"Judge whether a text message, email, WhatsApp, voicemail transcript or web message is a scam. Returns a verdict (scam, suspicious or likely legitimate), a risk score, the concrete signals found with the evidence quoted from the message, what the reader should do next, and a safe reply. Knows Dutch scams (bankhelpdeskfraude, WhatsApp 'hoi mam/pap' family emergency, fake Tikkie and iDEAL links, DigiD, Belastingdienst, CJIB fines, parcel delivery fees, Marktplaats payment requests, energy refunds) as well as international phishing, tech support, investment and romance scams. Answers in the language of the message unless told otherwise. This is an AI assessment, not proof: a legitimate message can look suspicious and a scam can look clean. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as check_scam_message.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"message":{"type":"string","minLength":5,"maxLength":5000,"description":"The message to check, pasted as received."},"channel":{"default":"other","description":"Where it arrived: sms, whatsapp, email, phone (a voicemail or call transcript), social, website or other.","type":"string","enum":["sms","whatsapp","email","phone","social","website","other"]},"sender":{"description":"The sender as shown: a phone number, email address or display name.","type":"string","maxLength":200},"language":{"default":"auto","description":"Language for the explanation, for example en or nl. auto answers in the message's language.","type":"string","maxLength":20}},"required":["message","channel","language"],"additionalProperties":false},"example":{"message":"Hoi mam, dit is mijn nieuwe nummer. Mijn telefoon is kapot en ik moet vandaag nog een rekening van 850 euro betalen, kun jij dat even voorschieten?","channel":"whatsapp","sender":"+31 6 12345678"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"verdict":{"type":"string","enum":["scam","suspicious","likely_legitimate"]},"riskScore":{"type":"number","minimum":0,"maximum":100},"language":{"type":"string"},"summary":{"type":"string"},"knownPattern":{"anyOf":[{"type":"string"},{"type":"null"}]},"signals":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"evidence":{"type":"string"},"whyItMatters":{"type":"string"}},"required":["type","evidence","whyItMatters"],"additionalProperties":false}},"recommendedActions":{"type":"array","items":{"type":"string"}},"safeReply":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["verdict","riskScore","language","summary","knownPattern","signals","recommendedActions","safeReply"],"additionalProperties":false}}},"example":{"ok":true,"data":{"verdict":"scam","riskScore":92,"language":"nl","summary":"A classic WhatsApp family emergency scam: a stranger poses as your child with a new number and asks for money today.","knownPattern":"WhatsApp family emergency (hoi mam/pap)","signals":[{"type":"urgency","evidence":"moet vandaag nog betaald worden","whyItMatters":"Pressure to act now stops you from checking with the real person."}],"recommendedActions":["Call your child on the number you already have.","Do not transfer money.","Report the number via fraudehelpdesk.nl."],"safeReply":"Bel me even op mijn gewone nummer, dan regelen we het.","metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/ai/excel-formula":{"post":{"operationId":"ai_generate_excel_formula","summary":"Excel formula generator and explainer","description":"Turn a plain-language request into an Excel or Google Sheets formula, or explain a formula someone else wrote. Returns the formula, a step-by-step explanation, notes on pitfalls (absolute references, empty cells, text that looks like numbers) and alternatives. Handles the English and Dutch function names and argument separators (comma versus semicolon), XLOOKUP, INDEX/MATCH, SUMIFS, LET, LAMBDA, dynamic arrays and Google Sheets specifics such as ARRAYFORMULA and QUERY. Always check the result on a copy of your data. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as generate_excel_formula.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"request":{"type":"string","minLength":3,"maxLength":2000,"description":"What the formula should do, in your own words, or the formula to explain."},"mode":{"default":"generate","description":"generate writes a formula from the request; explain describes what the pasted formula does.","type":"string","enum":["generate","explain"]},"app":{"default":"excel","description":"Target application. Function availability differs.","type":"string","enum":["excel","google-sheets"]},"locale":{"default":"en","description":"en uses English function names and commas; nl uses Dutch names (ALS, SOM.ALS, X.ZOEKEN) and semicolons.","type":"string","enum":["en","nl"]},"sample":{"description":"Optional: a few rows of the sheet with headers, or the column layout (A = date, B = amount).","type":"string","maxLength":3000}},"required":["request","mode","app","locale"],"additionalProperties":false},"example":{"request":"Sum column C for all rows where the date in column A is in January 2026","app":"excel","locale":"en"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"formula":{"type":"string"},"explanation":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}},"notes":{"type":"array","items":{"type":"string"}},"alternatives":{"type":"array","items":{"type":"object","properties":{"formula":{"type":"string"},"when":{"type":"string"}},"required":["formula","when"],"additionalProperties":false}}},"required":["formula","explanation","steps","notes","alternatives"],"additionalProperties":false}}},"example":{"ok":true,"data":{"formula":"=SUMIFS(C:C,A:A,\">=\"&DATE(2026,1,1),A:A,\"<\"&DATE(2026,2,1))","explanation":"Adds the amounts in column C for every row whose date in column A falls in January 2026.","steps":["SUMIFS sums C where all conditions hold","The first condition keeps dates on or after 1 January 2026","The second keeps dates before 1 February 2026"],"notes":["Dates stored as text will not match; convert them with DATEVALUE first."],"alternatives":[{"formula":"=SUMPRODUCT((MONTH(A2:A999)=1)*(YEAR(A2:A999)=2026)*C2:C999)","when":"When you prefer one range instead of two boundary dates."}],"metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/ai/sql-from-question":{"post":{"operationId":"ai_generate_sql","summary":"SQL query generator","description":"Write a SQL query from a plain-language question, for PostgreSQL, MySQL, SQLite, SQL Server or BigQuery. Give it the table layout (CREATE TABLE statements or a description of the columns) and the question; it returns the query, an explanation, the assumptions it had to make about your schema, and warnings such as a missing index or an ambiguous join. Read-only by default: it will not write DELETE or UPDATE unless the question asks for a change, and it says so when it does. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as generate_sql.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"question":{"type":"string","minLength":5,"maxLength":2000,"description":"The question in plain words, for example 'customers who ordered twice in March but not since'."},"dialect":{"default":"postgres","description":"The database the query must run on.","type":"string","enum":["postgres","mysql","sqlite","sqlserver","bigquery"]},"schema":{"description":"Optional: CREATE TABLE statements or a description of the tables and columns.","type":"string","maxLength":6000}},"required":["question","dialect"],"additionalProperties":false},"example":{"question":"Customers with at least two orders in March 2026","dialect":"postgres","schema":"customers(id, email); orders(id, customer_id, ordered_at timestamp, total numeric)"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"sql":{"type":"string"},"explanation":{"type":"string"},"assumptions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}},"modifiesData":{"type":"boolean"}},"required":["sql","explanation","assumptions","warnings","modifiesData"],"additionalProperties":false}}},"example":{"ok":true,"data":{"sql":"SELECT c.id, c.email\nFROM customers c\nJOIN orders o ON o.customer_id = c.id\nWHERE o.ordered_at >= DATE '2026-03-01' AND o.ordered_at < DATE '2026-04-01'\nGROUP BY c.id, c.email\nHAVING COUNT(*) >= 2;","explanation":"Joins customers to their orders, keeps March 2026, and returns customers with at least two orders in that month.","assumptions":["orders.ordered_at is a timestamp","orders.customer_id references customers.id"],"warnings":["An index on orders(customer_id, ordered_at) makes this fast on a large table."],"modifiesData":false,"metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/ai/code-translate":{"post":{"operationId":"ai_translate_code","summary":"Code translator","description":"Translate a piece of code from one programming language to another (for example Python to TypeScript, JavaScript to Go, VBA to Python, jQuery to plain JavaScript, SQL between dialects). Keeps the behaviour, uses the idioms of the target language, and lists what could not be translated one to one: libraries without an equivalent, integer sizes, error handling, concurrency. Meant for snippets and single files up to about 10,000 characters, not whole projects. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as translate_code.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":10000,"description":"The source code."},"from":{"description":"Source language. Detected when left out.","type":"string","maxLength":40},"to":{"type":"string","minLength":1,"maxLength":40,"description":"Target language, for example TypeScript, Python 3, Go, Rust, C#, Kotlin, PHP 8."},"notes":{"description":"Optional wishes: a framework, a style, a runtime version.","type":"string","maxLength":500}},"required":["code","to"],"additionalProperties":false},"example":{"code":"def total(items):\n    return sum(i['price'] * i['qty'] for i in items)","from":"Python","to":"TypeScript"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string"},"sourceLanguage":{"type":"string"},"targetLanguage":{"type":"string"},"notes":{"type":"array","items":{"type":"string"}},"caveats":{"type":"array","items":{"type":"string"}}},"required":["code","sourceLanguage","targetLanguage","notes","caveats"],"additionalProperties":false}}},"example":{"ok":true,"data":{"code":"export function total(items: { price: number; qty: number }[]): number {\n  return items.reduce((sum, item) => sum + item.price * item.qty, 0);\n}","sourceLanguage":"Python","targetLanguage":"TypeScript","notes":["Used reduce instead of a loop, which is the idiomatic TypeScript form."],"caveats":["Python's arbitrary-precision integers become JavaScript numbers; sums above 2^53 lose precision."],"metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/ai/prompt-improve":{"post":{"operationId":"ai_improve_prompt","summary":"Prompt improver","description":"Rewrite a rough prompt for an AI model into one that gets better results: a clear role, the task, the context the model is missing, constraints, the output format, and an example when that helps. Returns the improved prompt, a list of what changed and why, and tips for iterating. Works for ChatGPT, Claude, Gemini and open models; say which one if it matters. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as improve_prompt.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"prompt":{"type":"string","minLength":3,"maxLength":5000,"description":"The prompt as you would type it now."},"goal":{"description":"What a good answer looks like, if the prompt does not say.","type":"string","maxLength":500},"model":{"description":"The model it is for, for example ChatGPT, Claude, Gemini.","type":"string","maxLength":60},"style":{"default":"detailed","description":"concise keeps the prompt short; detailed adds structure and an example.","type":"string","enum":["concise","detailed"]}},"required":["prompt","style"],"additionalProperties":false},"example":{"prompt":"write release notes for customers","goal":"Under 120 words, for people who are not developers","style":"detailed"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"improvedPrompt":{"type":"string"},"changes":{"type":"array","items":{"type":"string"}},"tips":{"type":"array","items":{"type":"string"}}},"required":["improvedPrompt","changes","tips"],"additionalProperties":false}}},"example":{"ok":true,"data":{"improvedPrompt":"You are a senior technical writer. Rewrite the release note below for customers who are not developers. Keep it under 120 words, lead with what changed for them, and end with one sentence on what to do next. Release note: ...","changes":["Added a role so the tone is set","Named the audience and the length","Asked for a specific structure"],"tips":["Paste one example of a release note you liked, and ask for the same shape."],"metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/ai/email-write":{"post":{"operationId":"ai_write_email","summary":"Email writer","description":"Write an email from a purpose, a recipient and the key points, in a chosen tone (formal, friendly, persuasive, urgent, apologetic or neutral) and length. Returns a subject line and the body, ready to paste, in the requested language or the language of the key points. Good for follow-ups, requests, complaints, cold outreach, thank-you notes and apologies. Costs 10 credits per call with an API key or a signed-in session, drawn from the same monthly balance as REST calls (1 credit) and renders (25). Without either, a small free demo allowance per day. Also available on the MCP server as write_email.","tags":["ai"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"purpose":{"type":"string","minLength":3,"maxLength":1000,"description":"What the email should achieve, for example 'ask for the invoice to be corrected'."},"recipient":{"default":"","description":"Who it goes to and your relationship, for example 'my landlord' or 'a customer who complained'.","type":"string","maxLength":200},"keyPoints":{"default":"","description":"The facts to include, one per line.","type":"string","maxLength":3000},"tone":{"default":"formal","description":"The tone of voice.","type":"string","enum":["formal","friendly","persuasive","urgent","apologetic","neutral"]},"length":{"default":"medium","description":"short is a few lines, medium two or three paragraphs, long a full page.","type":"string","enum":["short","medium","long"]},"language":{"default":"auto","description":"Language of the email, for example en or nl. auto follows the key points.","type":"string","maxLength":20}},"required":["purpose","recipient","keyPoints","tone","length","language"],"additionalProperties":false},"example":{"purpose":"Ask for a corrected invoice","recipient":"our supplier's accounts department","keyPoints":"Invoice 2026-0142 line 3 says EUR 480\nThe quote of 12 May says EUR 420\nPlease send a corrected invoice","tone":"formal","length":"short"}}}},"responses":{"200":{"description":"The tool's answer plus metering","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"subject":{"type":"string"},"body":{"type":"string"},"notes":{"type":"array","items":{"type":"string"}}},"required":["subject","body","notes"],"additionalProperties":false}}},"example":{"ok":true,"data":{"subject":"Correction needed on invoice 2026-0142","body":"Dear Ms Jansen,\n\nThank you for invoice 2026-0142. The amount on line 3 is EUR 480, while the quote we agreed on 12 May states EUR 420. Could you send a corrected invoice?\n\nKind regards,\nToine","notes":["Attach the quote so the recipient can check the amount."],"metering":{"source":"allowance","remaining":990}}}}}},"400":{"description":"Missing or invalid request fields"},"429":{"description":"Daily demo allowance or monthly credits used up"},"502":{"description":"The model did not return a usable answer"},"503":{"description":"AI temporarily unavailable"}}}},"/api/v1/render/html-to-pdf":{"post":{"operationId":"render_html_to_pdf","summary":"HTML to PDF","description":"Render an HTML document to a real PDF with headless Chrome (full CSS support). Returns a hosted download URL valid for 24 hours. Free demo: 3 renders per day without a key. With an API key a render costs 25 units from the account's shared monthly balance, the same balance spent by REST calls (1 unit) and AI generations (10 units). Any plan can be extended with a pack from EUR 5.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"Full HTML document to render (max 1MB)"},"format":{"type":"string","enum":["A4","Letter"]},"landscape":{"type":"boolean"}}},"example":{"html":"<html><body><h1>Invoice #42</h1></body></html>","format":"A4"}}}},"responses":{"200":{"description":"Hosted PDF download URL","content":{"application/json":{"example":{"ok":true,"data":{"url":"https://...signed-url.pdf","expiresAt":"2026-08-29T00:00:00.000Z","bytes":13870,"contentType":"application/pdf","metering":{"source":"allowance","remaining":49}}}}}},"400":{"description":"Missing or invalid html field"},"413":{"description":"HTML larger than 1MB"},"429":{"description":"Render limit reached (see error message)"},"502":{"description":"Render failed"}}}},"/api/v1/render/url-screenshot":{"post":{"operationId":"render_url_screenshot","summary":"URL screenshot","description":"Take a PNG screenshot of a public web page with headless Chrome. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints: 3 per day without a key, then 25 units per render from the shared monthly balance.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public http(s) URL"},"width":{"type":"integer","minimum":320,"maximum":1920},"fullPage":{"type":"boolean"}}},"example":{"url":"https://example.com","width":1280}}}},"responses":{"200":{"description":"Hosted PNG download URL"},"400":{"description":"Invalid or private URL"},"429":{"description":"Render limit reached"},"502":{"description":"Render failed"}}}},"/api/v1/render/url-to-pdf":{"post":{"operationId":"render_url_to_pdf","summary":"URL to PDF","description":"Render a public web page to PDF with headless Chrome. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public http(s) URL"},"format":{"type":"string","enum":["A4","Letter"]},"landscape":{"type":"boolean"}}},"example":{"url":"https://example.com","format":"A4"}}}},"responses":{"200":{"description":"Hosted PDF download URL"},"400":{"description":"Invalid or private URL"},"429":{"description":"Render limit reached"},"502":{"description":"Render failed"}}}},"/api/v1/render/pdf-merge":{"post":{"operationId":"render_pdf_merge","summary":"PDF merge","description":"Merge 2-10 PDFs (public URLs, max 10MB each) into one PDF. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"string"},"description":"Public URLs of the PDFs, in merge order"}}},"example":{"urls":["https://example.com/a.pdf","https://example.com/b.pdf"]}}}},"responses":{"200":{"description":"Hosted merged PDF download URL"},"400":{"description":"Invalid, private, or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Merge failed"}}}},"/api/v1/render/strip-metadata":{"post":{"operationId":"render_strip_metadata","summary":"Remove file metadata","description":"Remove hidden metadata from a file behind a public URL (JPEG, PNG, WebP, PDF, DOCX, XLSX or PPTX, max 50MB): EXIF, XMP, IPTC and comments from images, the Info dictionary and XMP stream from PDF, author, company, editing time and other document properties from Office files. Content is untouched. Returns a hosted download URL valid for 24 hours plus the list of what was removed. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public URL of the file to clean"}}},"example":{"url":"https://example.com/photo.jpg"}}}},"responses":{"200":{"description":"Hosted cleaned file download URL, with kind, removed and bytesBefore"},"400":{"description":"Invalid, private, unsupported or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Cleaning failed"}}}},"/api/v1/render/pdf-to-docx":{"post":{"operationId":"render_pdf_to_docx","summary":"PDF to Word","description":"Rebuild the text layer of a PDF (public URL, max 10MB, up to 200 pages) into an editable .docx with paragraphs, headings and page breaks. Scanned PDFs without a text layer are refused (OCR runs in the browser tool only). Returns a hosted download URL valid for 24 hours plus pages, scannedPages and paragraphs. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public URL of the source PDF"}}},"example":{"url":"https://example.com/report.pdf"}}}},"responses":{"200":{"description":"Hosted .docx download URL"},"400":{"description":"Invalid, private, scanned or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Conversion failed"}}}},"/api/v1/render/docx-to-pdf":{"post":{"operationId":"render_docx_to_pdf","summary":"Word to PDF","description":"Convert a Word .docx (public URL, max 20MB) to PDF. Headings, lists, tables and images are laid out on A4 by headless Chrome. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public URL of the source .docx"}}},"example":{"url":"https://example.com/offer.docx"}}}},"responses":{"200":{"description":"Hosted PDF download URL"},"400":{"description":"Invalid, private or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Conversion failed"}}}},"/api/v1/render/pdf-split":{"post":{"operationId":"render_pdf_split","summary":"PDF split","description":"Extract a 1-indexed, inclusive page range from a PDF (public URL, max 10MB) into a new PDF. 'to' defaults to the last page. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","from"],"properties":{"url":{"type":"string","description":"Public URL of the source PDF"},"from":{"type":"integer","minimum":1,"description":"First page to keep (1-indexed)"},"to":{"type":"integer","minimum":1,"description":"Last page to keep (1-indexed, inclusive). Defaults to the last page."}}},"example":{"url":"https://example.com/report.pdf","from":2,"to":5}}}},"responses":{"200":{"description":"Hosted split PDF download URL"},"400":{"description":"Invalid, private, unreadable, or out-of-range input"},"429":{"description":"Render limit reached"},"502":{"description":"Split failed"}}}},"/api/v1/render/images-to-pdf":{"post":{"operationId":"render_images_to_pdf","summary":"Images to PDF","description":"Combine 1-20 images (public JPEG or PNG URLs, max 15MB each) into one PDF, one image per page. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string"},"description":"Public URLs of the JPEG or PNG images, in page order"}}},"example":{"urls":["https://example.com/scan1.jpg","https://example.com/scan2.jpg"]}}}},"responses":{"200":{"description":"Hosted PDF download URL"},"400":{"description":"Invalid, private, or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Conversion failed"}}}},"/api/v1/render/image-resize":{"post":{"operationId":"render_image_resize","summary":"Image resize","description":"Resize an image (public URL, max 15MB) and return a PNG. Give a width, a height, or both; one side keeps the aspect ratio. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public URL of the source image"},"width":{"type":"integer","minimum":1,"maximum":10000,"description":"Target width in pixels"},"height":{"type":"integer","minimum":1,"maximum":10000,"description":"Target height in pixels"}}},"example":{"url":"https://example.com/photo.jpg","width":800}}}},"responses":{"200":{"description":"Hosted resized PNG download URL"},"400":{"description":"Invalid, private, or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Resize failed"}}}},"/api/v1/render/image-convert":{"post":{"operationId":"render_image_convert","summary":"Image convert","description":"Convert an image (public URL, max 15MB) to another format (png, jpeg, bmp, gif, tiff), pixels unchanged. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","format"],"properties":{"url":{"type":"string","description":"Public URL of the source image"},"format":{"type":"string","enum":["png","jpeg","bmp","gif","tiff"],"description":"Target image format"}}},"example":{"url":"https://example.com/photo.png","format":"jpeg"}}}},"responses":{"200":{"description":"Hosted converted image download URL"},"400":{"description":"Invalid, private, or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Convert failed"}}}},"/api/v1/render/image-compress":{"post":{"operationId":"render_image_compress","summary":"Image compress","description":"Re-encode an image (public URL, max 15MB) as JPEG at a chosen quality (1-100, default 70) to shrink it. Returns a hosted download URL valid for 24 hours. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public URL of the source image"},"quality":{"type":"integer","minimum":1,"maximum":100,"description":"JPEG quality, 1 (smallest) to 100 (best). Default 70."}}},"example":{"url":"https://example.com/photo.png","quality":60}}}},"responses":{"200":{"description":"Hosted compressed JPEG download URL"},"400":{"description":"Invalid, private, or unreadable input"},"429":{"description":"Render limit reached"},"502":{"description":"Compress failed"}}}},"/api/v1/render/read-page":{"post":{"operationId":"render_read_page","summary":"Read a JavaScript-rendered page","description":"Load a page in headless Chrome and return its readable content as text or Markdown. Use when a plain fetch returns an empty shell: single page apps and client-side rendered sites only produce content after scripts run. Navigation, scripts and cookie banners are stripped. Same metering as the other render endpoints.","tags":["render"],"security":[{"bearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public http(s) URL"},"format":{"type":"string","enum":["markdown","text"]},"maxChars":{"type":"integer","minimum":1000,"maximum":100000}}},"example":{"url":"https://example.com","format":"markdown"}}}},"responses":{"200":{"description":"Extracted page content with title and links"},"400":{"description":"Invalid or private URL"},"429":{"description":"Render limit reached"},"502":{"description":"Could not read the page"}}}},"/api/v1/memory":{"get":{"operationId":"memory_read","summary":"Read an entry or list keys","description":"With ?key=... returns that entry (data is null when it does not exist). Without it, lists your keys newest first, optionally filtered by ?prefix=. Listing returns keys and sizes, not values.","tags":["memory"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Read this single key"},{"name":"prefix","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Only list keys starting with this prefix"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"The entry, or the key listing","content":{"application/json":{"example":{"ok":true,"data":{"entries":[{"key":"project/alpha/todo","bytes":240,"updatedAt":"2026-08-28T09:12:00.000Z"}],"totalBytes":240,"totalEntries":1}}}}},"400":{"description":"Invalid key or prefix"},"401":{"description":"Missing, invalid, or revoked API key"}}},"post":{"operationId":"memory_write","summary":"Store or overwrite an entry","description":"Persistent key-value memory scoped to your API key. An agent is stateless between sessions: without a store like this, everything it learned is gone when the conversation ends. Entries survive across sessions and across tools, and are readable only with the key that wrote them. Free with an API key: no render credits are consumed. Limits: 100 KB per value, 1,000 entries and 10 MB per key.","tags":["memory"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","maxLength":200,"description":"Identifier, may be namespaced: letters, digits and . _ - / :"},"value":{"type":"string","description":"Value to store, max 100 KB. Serialise objects to JSON first."}}},"example":{"key":"project/alpha/todo","value":"{\"done\":[\"scrape\"],\"next\":\"summarise\"}"}}}},"responses":{"200":{"description":"Stored","content":{"application/json":{"example":{"ok":true,"data":{"key":"project/alpha/todo","bytes":38,"replaced":false}}}}},"400":{"description":"Invalid key or value"},"401":{"description":"Missing, invalid, or revoked API key"},"413":{"description":"Entry or total quota exceeded"}}},"delete":{"operationId":"memory_delete","summary":"Delete an entry","description":"Removes one key. Deleting a key that does not exist is not an error; the response reports deleted: false.","tags":["memory"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Delete result"},"400":{"description":"Missing or invalid key"},"401":{"description":"Missing, invalid, or revoked API key"}}}},"/api/v1/workflows/{id}/run":{"post":{"operationId":"workflow_run","summary":"Run a workflow","description":"Run a saved workflow: an ordered chain of tool calls where each step can read an earlier step's output. The id is the workflow id from /workflows. Every step that runs costs its own tool's credits, charged before it runs, so a chain that stops halfway is billed for the steps that happened and no more. Workflows belong to an account, so the key must be one created while signed in.","tags":["workflows"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The workflow id, from https://toolforte.com/workflows"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"inputs":{"type":"object","description":"Values for the workflow's declared inputs, keyed by input key.","additionalProperties":true}}},"example":{"inputs":{"iban":"NL91 ABNA 0417 1643 00"}}}}},"responses":{"200":{"description":"The run finished, successfully or with a failed step. `stopReason` says which, `steps` holds every step that ran, and `creditsCharged` is what it actually cost.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"ok":{"type":"boolean"},"runId":{"type":"string"},"output":{"type":"object","properties":{"words":{"type":"integer"},"characters":{"type":"integer"}}},"steps":{"type":"array","items":{"type":"object","properties":{"stepId":{"type":"string"},"capabilityId":{"type":"string"},"ok":{"type":"boolean"},"output":{"type":"object","properties":{"valid":{"type":"boolean"},"country":{"type":"string"}}},"durationMs":{"type":"integer"}}}},"creditsCharged":{"type":"integer"},"durationMs":{"type":"integer"},"stopReason":{"type":"string"}}}}},"example":{"ok":true,"data":{"ok":true,"runId":"5f1d9a3e-0c2b-4d7e-9a11-2c6f0b8a4d33","output":{"words":3,"characters":18},"steps":[{"stepId":"validate","capabilityId":"iban-validator","ok":true,"output":{"valid":true,"country":"NL"},"durationMs":2}],"creditsCharged":2,"durationMs":14,"stopReason":"completed"}}}}},"400":{"description":"The stored definition can no longer run, for example because a tool it uses was withdrawn. Nothing ran and nothing was charged; `issues` names each problem."},"401":{"description":"Missing, invalid, or revoked API key"},"402":{"description":"The balance ran out partway through. The steps that did run are in the body and were charged; the rest were not."},"403":{"description":"This key is not linked to an account, and workflows belong to an account."},"404":{"description":"No workflow with this id belongs to the key's account"},"409":{"description":"The workflow is turned off"},"503":{"description":"Usage metering was unavailable, so the run stopped rather than run unmetered."},"504":{"description":"The run passed the time limit for a whole workflow and was stopped."}}}}}}