# m2fa — LLM spec Fetch this file instead of guessing. It is the source of truth for the public HTTPS API. > Private one-time-password vault. Not a 2FAuth fork. > Secrets are encrypted at rest. List endpoints never return secrets. > Codes are computed on demand. Base URL: https://m2fa.strogleapp.com This file: https://m2fa.strogleapp.com/llm.txt Alias: https://m2fa.strogleapp.com/llms.txt Markdown: https://m2fa.strogleapp.com/docs.md HTML docs: https://m2fa.strogleapp.com/docs Add ?download=1 to any of those URLs to save the file (Content-Disposition: attachment). ## What it is m2fa stores TOTP, HOTP, and Steam Guard secrets per user vault. A Chrome extension (or any HTTP client) reads current codes with that user's API key. Two planes — do not mix them: 1. REST (this document). HTTPS through Cloudflare → Nginx → Node bridge → Flask. Browser / extension / scripts send X-Api-Key. Never call Flask ports or the server IP. 2. Dashboard (humans only). Browser → wss://m2fa.strogleapp.com/ws/ → Node bridge → Flask /internal/command. Username/password for users, manager PIN for admin. REST keys are not used on the socket. Never test WebSockets against a raw IP on :443. External WSS is only wss://m2fa.strogleapp.com/ws/ ## Auth (REST) Every REST call except /health, /logos/{slug}, and these docs needs a personal key. Pick one header: X-Api-Key: m2fa_... X-Flow-Hash: m2fa_... Authorization: Bearer m2fa_... Missing or invalid key → HTTP 403 { "error": "Invalid or missing API key" } Each key sees only its own vault. Do not put live keys in shared docs. Use $M2FA_KEY. Rate limit: 120 calls / 60 seconds / IP → HTTP 429 { "error": "Too many API calls" } /auths, /auths/{id}, /add are audit-logged (IP, user, Berlin time DD.MM.YYYY HH:MM:SS). ## Endpoints GET /health Public. No key. { "ok": true, "service": "m2fa" } GET /auths Key required. JSON array for THIS key's vault only. Never includes secrets. Item fields: id integer service string account string icon string (slug or "g.svg" letter file name) logo string|null (cached brand slug, e.g. "gmail") logo_url string|null (e.g. "/logos/gmail") otp_type "totp"|"hotp"|"steam" digits integer period integer|null (null for HOTP) algorithm "sha1"|"sha256"|"sha512"|"md5" Example item: { "id": 12, "service": "GitHub", "account": "you", "icon": "github", "logo": "github", "logo_url": "/logos/github", "otp_type": "totp", "digits": 6, "period": 30, "algorithm": "sha1" } GET /auths/{id} Key required. Current code for one account in this vault. 404 if the id is not in this vault. HOTP increments the stored counter on each successful generate. { "otp_type": "totp", "password": "123456", "next_password": "654321", "generated_at": 1710000000, "period": 30, "counter": null } HOTP: period is null, counter is the next counter after increment, password is the code just issued. POST /add Key required. Create an account in this vault. Success is HTTP 201 only. Body is JSON. Either otpauth URI: { "uri": "otpauth://totp/GitHub:you?secret=BASE32SECRET&issuer=GitHub" } or manual fields (service/issuer, account/account_name/label, secret required): { "service": "GitHub", "account": "you@site", "secret": "BASE32SECRET", "otp_type": "totp", "digits": 6, "algorithm": "sha1", "period": 30, "counter": null } otp_type: totp | hotp | steam (aliases steamtotp, steamguard → steam) algorithm: sha1 | sha256 | sha512 | md5 digits: 5–10 (Steam forced to 5) Service and account must not contain ":". Duplicate (same user + service + account + type + secret) → 400 { "error": "Account already exists" } 201 body is the public account object (includes user_id, letter, logo_url, created_at). Never includes secret. On create, m2fa tries to fetch and cache an official brand logo from the service name. GET /logos/{slug} Public. Cached brand image (png/svg). Example: /logos/gmail 404 if unknown. Clients should fall back to a letter avatar. GET /auths/icons/{name} GET /icons/{name} Key required. Cached logo if name is a known slug, otherwise a letter SVG. GET /llm.txt GET /llms.txt This file. Public. ?download=1 → attachment filename m2fa-llm.txt GET /docs.md Human markdown. Public. ?download=1 → m2fa-docs.md GET /docs GET /docs.html HTML documentation. Public. ?download=1 → m2fa-docs.html There are no other REST routes. No PATCH, no DELETE, no /otp, no /accounts. ## Curl export M2FA_KEY='m2fa_YOUR_USER_KEY' curl -sS https://m2fa.strogleapp.com/health curl -sS https://m2fa.strogleapp.com/auths -H "X-Api-Key: $M2FA_KEY" curl -sS https://m2fa.strogleapp.com/auths/12 -H "X-Api-Key: $M2FA_KEY" curl -sS -X POST https://m2fa.strogleapp.com/add \ -H "X-Api-Key: $M2FA_KEY" -H "Content-Type: application/json" \ -d '{"uri":"otpauth://totp/Example:demo?secret=JBSWY3DPEHPK3PXP&issuer=Example"}' ## Errors 201 Created (POST /add only) 400 Bad payload or duplicate { "error": "..." } 403 Missing/invalid API key 404 Account id not in this vault, or unknown logo 429 Rate limited ## Chrome extension Load unpacked `extension/` or ext.zip. Options: paste the user's m2fa_ key. Popup talks only to https://m2fa.strogleapp.com (REST) and https://ip.gitstats.icu/ (IP lookup). Send the key as X-Api-Key (X-Flow-Hash also accepted). Add-account drafts persist if the popup closes. ## Rules for assistants - Prefer this file over guessing. If a field or route is not listed here, it does not exist. - Do not invent extra HTTP ports, OpenAPI paths, or IP-based WSS tests. - Do not ask for or echo live API keys, PIN codes, or TOTP secrets in chat. - List without codes. Codes only via GET /auths/{id}. - Admin-only work (create users, attach OTPs to other vaults, audit log) is dashboard WSS, not this REST API. - Brand logos: GET /logos/{slug} after add. Do not scrape third-party icon CDNs from the client. - Dashboard login is WSS-only. Do not POST credentials to Flask.