diff --git a/.claude/evals/_lib.py b/.claude/evals/_lib.py new file mode 100644 index 0000000..d3c7819 --- /dev/null +++ b/.claude/evals/_lib.py @@ -0,0 +1,251 @@ +"""Shared utilities for Mintlify docs evals and hooks.""" + +import json +import os +import re +import sys +from pathlib import Path + +# ── Paths ────────────────────────────────────────────────────────────── + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +DOCS_JSON = REPO_ROOT / "docs.json" +TEMPLATES_DIR = REPO_ROOT / ".claude" / "templates" + +EXCLUDED_PREFIXES = ( + ".claude/", ".github/", ".idea/", "node_modules/", + "drafts/", "images/", "logo/", "apis/", "snippets/", +) + +EXCLUDED_FILES = {"README.md", "CONTRIBUTING.md", "CLAUDE.md", "LICENSE"} + +# ── Page type inference ──────────────────────────────────────────────── + +PAGE_TYPE_RULES = [ + ("index.mdx", "landing"), + ("quickstart.mdx", "quickstart"), + ("guides/principles/", "principle"), + ("guides/", "guide"), + ("journeys/", "journey"), + ("webhooks/overview.mdx", "webhook-overview"), + ("webhooks/events.mdx", "webhook-events"), + ("reference/errors-catalog.mdx", "errors-catalog"), + ("reference/", "reference-overview"), +] + + +def infer_page_type(rel_path: str) -> str | None: + """Infer the page type from a file's relative path.""" + for pattern, page_type in PAGE_TYPE_RULES: + if pattern.endswith("/"): + if rel_path.startswith(pattern): + return page_type + else: + if rel_path == pattern: + return page_type + return None + + +def get_template_path(page_type: str) -> Path | None: + """Get the template file for a page type.""" + p = TEMPLATES_DIR / f"{page_type}.mdx" + return p if p.exists() else None + + +# ── Frontmatter parsing ─────────────────────────────────────────────── + +FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) + + +def parse_frontmatter(text: str) -> dict: + """Parse YAML frontmatter from MDX content. Simple subset parser.""" + m = FRONTMATTER_RE.match(text) + if not m: + return {} + fm = {} + for line in m.group(1).splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if ":" in line: + key, _, val = line.partition(":") + key = key.strip() + val = val.strip().strip('"').strip("'") + fm[key] = val + return fm + + +# ── Content analysis ────────────────────────────────────────────────── + +HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$", re.MULTILINE) +SECTION_HEADING_RE = re.compile(r"^##\s+(.+)$", re.MULTILINE) +TODO_RE = re.compile(r"\{/\*\s*TODO:", re.IGNORECASE) +PLACEHOLDER_PATTERNS = ["todo", "tbd", "placeholder", "fill in", "description here", "{{", "..."] + +MIN_DESCRIPTION_LENGTH = 20 +SUBSTANTIVE_WORD_COUNT = 200 +THIN_WORD_COUNT = 30 + + +def extract_headings(text: str) -> list[tuple[int, str]]: + """Extract (level, title) from markdown headings.""" + return [(len(m.group(1)), m.group(2).strip()) for m in HEADING_RE.finditer(text)] + + +def extract_body(text: str) -> str: + """Extract content after frontmatter, stripping MDX comments.""" + m = FRONTMATTER_RE.match(text) + body = text[m.end():] if m else text + # Strip MDX comments + body = re.sub(r"\{/\*.*?\*/\}", "", body, flags=re.DOTALL) + return body.strip() + + +def word_count(text: str) -> int: + """Count words in text, excluding markdown syntax and component tags.""" + # Remove component tags + clean = re.sub(r"<[^>]+>", "", text) + # Remove markdown links, images + clean = re.sub(r"!?\[[^\]]*\]\([^)]*\)", "", clean) + # Remove code blocks + clean = re.sub(r"```[\s\S]*?```", "", clean) + # Remove inline code + clean = re.sub(r"`[^`]+`", "", clean) + # Remove headings markers + clean = re.sub(r"^#{1,6}\s+", "", clean, flags=re.MULTILINE) + return len(clean.split()) + + +def is_placeholder(value: str) -> bool: + """Check if a string is a placeholder.""" + lower = value.lower().strip() + return any(p in lower for p in PLACEHOLDER_PATTERNS) + + +def has_todo_only(text: str) -> bool: + """Check if body is empty except for TODO comments.""" + body = extract_body(text) + return len(body) == 0 or (TODO_RE.search(text) and word_count(body) < 5) + + +# ── docs.json navigation ────────────────────────────────────────────── + +def load_docs_json() -> dict: + """Load and return docs.json.""" + with open(DOCS_JSON) as f: + return json.load(f) + + +def extract_nav_pages(docs: dict) -> set[str]: + """Extract all page paths referenced in docs.json navigation. + + Returns paths without .mdx extension (as they appear in docs.json). + Skips OpenAPI operation references like 'GET /api/...' + """ + pages = set() + + def walk(obj): + if isinstance(obj, str): + # Skip OpenAPI operation references + if not re.match(r"^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+/", obj): + pages.add(obj) + elif isinstance(obj, list): + for item in obj: + walk(item) + elif isinstance(obj, dict): + if "pages" in obj: + walk(obj["pages"]) + if "groups" in obj: + walk(obj["groups"]) + if "tabs" in obj: + walk(obj["tabs"]) + + nav = docs.get("navigation", {}) + walk(nav) + return pages + + +# ── File collection ─────────────────────────────────────────────────── + +def collect_mdx_files() -> list[Path]: + """Collect all .mdx files in the repo, excluding non-content dirs.""" + files = [] + for p in REPO_ROOT.rglob("*.mdx"): + rel = str(p.relative_to(REPO_ROOT)) + if any(rel.startswith(prefix) for prefix in EXCLUDED_PREFIXES): + continue + files.append(p) + return sorted(files) + + +def rel_path(p: Path) -> str: + """Get path relative to repo root.""" + return str(p.relative_to(REPO_ROOT)) + + +# ── Output formatting ───────────────────────────────────────────────── + +def output_result(name: str, score: float, total: int, passed: int, + issues: list[dict], threshold: float = 0.0): + """Print eval result as JSON to stdout, human summary to stderr.""" + result = { + "eval": name, + "score": round(score, 1), + "total": total, + "passed": passed, + "failed": total - passed, + "threshold": threshold, + "pass": score >= threshold, + "issues": issues, + } + json.dump(result, sys.stdout, indent=2) + print(file=sys.stdout) + + # Human-readable summary to stderr + status = "PASS" if score >= threshold else "FAIL" + print(f"[{status}] {name}: {score:.1f}% ({passed}/{total})", file=sys.stderr) + if issues and score < 80: + for issue in issues[:10]: + print(f" - {issue.get('file', '?')}: {issue.get('message', '?')}", file=sys.stderr) + if len(issues) > 10: + print(f" ... and {len(issues) - 10} more", file=sys.stderr) + + +# ── Hook helpers ─────────────────────────────────────────────────────── + +def load_hook_input() -> tuple[str, str] | None: + """Read PostToolUse hook JSON from stdin and return (file_path, content). + + Returns None when the file should be skipped (non-MDX, snippet, template). + Handles both Write (content in payload) and Edit (read from disk) tools. + """ + hook_input = json.load(sys.stdin) + tool_input = hook_input.get("tool_input", {}) + file_path = tool_input.get("file_path", "") + + if not file_path.endswith(".mdx"): + return None + + if "/snippets/" in file_path or "/.claude/" in file_path: + return None + + content = tool_input.get("content", "") + if not content: + try: + with open(file_path) as f: + content = f.read() + except (FileNotFoundError, PermissionError): + return None + + return file_path, content + + +def abs_to_rel(file_path: str) -> str: + """Convert an absolute file path to a path relative to the repo root. + + Falls back to the filename if the path is not under REPO_ROOT. + """ + try: + return str(Path(file_path).resolve().relative_to(REPO_ROOT)) + except ValueError: + return Path(file_path).name diff --git a/.claude/evals/content-depth.py b/.claude/evals/content-depth.py new file mode 100644 index 0000000..becfab7 --- /dev/null +++ b/.claude/evals/content-depth.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Eval: detect stub pages vs. substantive content. + +Scoring: + - Substantive (200+ words of real prose): PASS + - Thin (30-199 words): WARN (counted as pass) + - Stub (<30 words or TODO-only): FAIL +""" + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import ( + collect_mdx_files, rel_path, extract_body, word_count, + has_todo_only, output_result, SUBSTANTIVE_WORD_COUNT, THIN_WORD_COUNT, +) + + +def check_file(path: Path) -> dict | None: + text = path.read_text(encoding="utf-8") + rp = rel_path(path) + body = extract_body(text) + wc = word_count(body) + + if has_todo_only(text) or wc < THIN_WORD_COUNT: + return { + "file": rp, + "words": wc, + "category": "stub", + "message": f"Stub page ({wc} words) — needs content", + } + return None # thin and substantive both pass + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=50.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + files = collect_mdx_files() + issues = [] + + for f in files: + issue = check_file(f) + if issue: + issues.append(issue) + + total = len(files) + passed = total - len(issues) + score = (passed / total * 100) if total > 0 else 100.0 + + output_result("content-depth", score, total, passed, issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/evals/description-quality.py b/.claude/evals/description-quality.py new file mode 100644 index 0000000..5e1f654 --- /dev/null +++ b/.claude/evals/description-quality.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Eval: description field must be present, non-placeholder, and meaningful.""" + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import ( + collect_mdx_files, rel_path, parse_frontmatter, + is_placeholder, output_result, MIN_DESCRIPTION_LENGTH, +) + + +def check_file(path: Path) -> dict | None: + text = path.read_text(encoding="utf-8") + fm = parse_frontmatter(text) + rp = rel_path(path) + + desc = fm.get("description", "") + if not desc: + return {"file": rp, "message": "Missing description"} + if len(desc) < MIN_DESCRIPTION_LENGTH: + return {"file": rp, "message": f"Description too short ({len(desc)} chars)"} + if is_placeholder(desc): + return {"file": rp, "message": f"Placeholder description: '{desc}'"} + return None + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=95.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + files = collect_mdx_files() + issues = [i for i in (check_file(f) for f in files) if i] + + total = len(files) + passed = total - len(issues) + score = (passed / total * 100) if total > 0 else 100.0 + + output_result("description-quality", score, total, passed, issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/evals/frontmatter-completeness.py b/.claude/evals/frontmatter-completeness.py new file mode 100644 index 0000000..4e06e51 --- /dev/null +++ b/.claude/evals/frontmatter-completeness.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Eval: every MDX page must have title and description in frontmatter.""" + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import ( + collect_mdx_files, rel_path, parse_frontmatter, + output_result, MIN_DESCRIPTION_LENGTH, +) + + +def check_file(path: Path) -> list[dict]: + issues = [] + text = path.read_text(encoding="utf-8") + fm = parse_frontmatter(text) + rp = rel_path(path) + + if not fm.get("title"): + issues.append({"file": rp, "field": "title", "message": "Missing title"}) + + desc = fm.get("description", "") + if not desc: + issues.append({"file": rp, "field": "description", "message": "Missing description"}) + elif len(desc) < MIN_DESCRIPTION_LENGTH: + issues.append({ + "file": rp, + "field": "description", + "message": f"Description too short ({len(desc)} chars, need {MIN_DESCRIPTION_LENGTH}+)", + }) + + return issues + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=95.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + files = collect_mdx_files() + all_issues = [] + + for f in files: + all_issues.extend(check_file(f)) + + total = len(files) + failed_files = {i["file"] for i in all_issues} + passed = total - len(failed_files) + score = (passed / total * 100) if total > 0 else 100.0 + + output_result("frontmatter-completeness", score, total, passed, all_issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/evals/navigation-coverage.py b/.claude/evals/navigation-coverage.py new file mode 100644 index 0000000..2f79fb2 --- /dev/null +++ b/.claude/evals/navigation-coverage.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Eval: every page in docs.json must have a corresponding .mdx file. + +Reverse of orphan-detector: catches stale navigation entries pointing +to files that don't exist (yet or anymore). +Skips OpenAPI operation references (e.g., 'GET /api/accounts/{id}'). +""" + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import ( + REPO_ROOT, load_docs_json, extract_nav_pages, output_result, +) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=90.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + docs = load_docs_json() + nav_pages = extract_nav_pages(docs) + issues = [] + + for page in sorted(nav_pages): + mdx_path = REPO_ROOT / f"{page}.mdx" + if not mdx_path.exists(): + issues.append({ + "page": page, + "message": f"docs.json references '{page}' but {page}.mdx does not exist", + }) + + total = len(nav_pages) + passed = total - len(issues) + score = (passed / total * 100) if total > 0 else 100.0 + + output_result("navigation-coverage", score, total, passed, issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/evals/openapi-completeness.py b/.claude/evals/openapi-completeness.py new file mode 100644 index 0000000..5cee564 --- /dev/null +++ b/.claude/evals/openapi-completeness.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Eval: detect TODO stubs in OpenAPI specs. + +Checks for: + - Operations with 'TODO' in description + - Operations missing request body schemas (POST/PUT/PATCH) + - Operations missing response schemas +""" + +import argparse +import json +import re +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import REPO_ROOT, output_result + +# Simple YAML value extractor (avoids PyYAML dependency) +TODO_RE = re.compile(r"TODO", re.IGNORECASE) + +APIS_DIR = REPO_ROOT / "apis" +METHODS_NEEDING_BODY = {"post", "put", "patch"} + + +def parse_openapi_operations(spec_path: Path) -> list[dict]: + """Extract operations from an OpenAPI YAML file. + + Returns list of {method, path, has_description_todo, has_request_body, has_response_schema}. + Uses a simple line-based parser to avoid PyYAML dependency. + """ + try: + import yaml + with open(spec_path) as f: + spec = yaml.safe_load(f) + except ImportError: + # Fallback: parse with json if it's JSON, otherwise line-scan for TODOs + try: + with open(spec_path) as f: + spec = json.load(f) + except (json.JSONDecodeError, ValueError): + return _line_scan_for_todos(spec_path) + + operations = [] + paths = spec.get("paths", {}) + for path, methods in paths.items(): + if not isinstance(methods, dict): + continue + for method, op in methods.items(): + if method.startswith("x-") or not isinstance(op, dict): + continue + desc = op.get("description", "") or "" + has_todo = bool(TODO_RE.search(desc)) + has_body = "requestBody" in op + responses = op.get("responses", {}) + has_response_schema = any( + "content" in (r if isinstance(r, dict) else {}) + for r in responses.values() + if isinstance(r, dict) and r.get("description", "") != "" + ) + operations.append({ + "method": method.upper(), + "path": path, + "has_description_todo": has_todo, + "has_request_body": has_body, + "has_response_schema": has_response_schema, + }) + return operations + + +def _line_scan_for_todos(spec_path: Path) -> list[dict]: + """Fallback: scan YAML for TODO strings without full parsing.""" + text = spec_path.read_text(encoding="utf-8") + operations = [] + current_path = None + current_method = None + for line in text.splitlines(): + stripped = line.strip() + # Detect path entries (indented under paths:) + if line.startswith(" /") and line.rstrip().endswith(":"): + current_path = stripped.rstrip(":") + elif line.startswith(" ") and stripped.split(":")[0] in ( + "get", "post", "put", "patch", "delete", "head", "options" + ): + current_method = stripped.split(":")[0].upper() + elif "TODO" in line and current_path and current_method: + operations.append({ + "method": current_method, + "path": current_path, + "has_description_todo": True, + "has_request_body": False, + "has_response_schema": False, + }) + return operations + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=50.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + issues = [] + total_ops = 0 + + for spec_file in sorted(APIS_DIR.rglob("openapi.*")): + service = spec_file.parent.name + operations = parse_openapi_operations(spec_file) + + for op in operations: + total_ops += 1 + op_id = f"{op['method']} {op['path']}" + + if op["has_description_todo"]: + issues.append({ + "service": service, + "operation": op_id, + "message": "Description contains TODO", + }) + + if op["method"] in METHODS_NEEDING_BODY and not op["has_request_body"]: + issues.append({ + "service": service, + "operation": op_id, + "message": f"{op['method']} operation missing requestBody schema", + }) + + if not op["has_response_schema"]: + issues.append({ + "service": service, + "operation": op_id, + "message": "Missing response content schema", + }) + + # Score: per-operation, each issue deducts from that operation + failed_ops = len({(i["service"], i["operation"]) for i in issues}) + passed = total_ops - failed_ops + score = (passed / total_ops * 100) if total_ops > 0 else 100.0 + + output_result("openapi-completeness", score, total_ops, passed, issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/evals/orphan-detector.py b/.claude/evals/orphan-detector.py new file mode 100644 index 0000000..883add5 --- /dev/null +++ b/.claude/evals/orphan-detector.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Eval: every .mdx file must appear in docs.json navigation.""" + +import argparse +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from _lib import ( + collect_mdx_files, rel_path, load_docs_json, + extract_nav_pages, output_result, +) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--threshold", type=float, default=90.0) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + docs = load_docs_json() + nav_pages = extract_nav_pages(docs) + files = collect_mdx_files() + issues = [] + + for f in files: + rp = rel_path(f) + # docs.json references pages without .mdx extension + page_path = rp.removesuffix(".mdx") + if page_path not in nav_pages: + issues.append({ + "file": rp, + "message": f"Not in docs.json navigation — orphan page", + }) + + total = len(files) + passed = total - len(issues) + score = (passed / total * 100) if total > 0 else 100.0 + + output_result("orphan-detector", score, total, passed, issues, args.threshold) + sys.exit(0 if score >= args.threshold else 1) + + +if __name__ == "__main__": + main() diff --git a/.claude/hooks/check-description.py b/.claude/hooks/check-description.py new file mode 100644 index 0000000..8a7840f --- /dev/null +++ b/.claude/hooks/check-description.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Hook: real-time description quality check on MDX writes.""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "evals")) +from _lib import load_hook_input, parse_frontmatter + +MARKETING_WORDS = [ + "powerful", "seamless", "robust", "cutting-edge", "world-class", + "best-in-class", "enterprise-grade", "next-generation", "revolutionary", +] + + +def main(): + result = load_hook_input() + if result is None: + sys.exit(0) + + _, content = result + fm = parse_frontmatter(content) + desc = fm.get("description", "") + + if not desc: + sys.exit(0) + + warnings = [] + desc_lower = desc.lower() + for word in MARKETING_WORDS: + if word in desc_lower: + warnings.append(f"Marketing language in description: '{word}'") + + if warnings: + print("DESCRIPTION QUALITY WARNING:", file=sys.stderr) + for w in warnings: + print(f" ⚠ {w}", file=sys.stderr) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.claude/hooks/validate-body-sections.py b/.claude/hooks/validate-body-sections.py new file mode 100644 index 0000000..4cb2f81 --- /dev/null +++ b/.claude/hooks/validate-body-sections.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Hook: enforce required body sections based on page type template. + +Non-blocking (warning only) — guides the writer without hard-blocking saves. +""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "evals")) +from _lib import ( + load_hook_input, abs_to_rel, infer_page_type, + FRONTMATTER_RE, SECTION_HEADING_RE, TEMPLATES_DIR, +) + + +def get_required_sections(page_type: str) -> list[str]: + template = TEMPLATES_DIR / f"{page_type}.mdx" + if not template.exists(): + return [] + text = template.read_text(encoding="utf-8") + m = FRONTMATTER_RE.match(text) + body = text[m.end():] if m else text + return [h.strip().lower() for h in SECTION_HEADING_RE.findall(body)] + + +def main(): + result = load_hook_input() + if result is None: + sys.exit(0) + + file_path, content = result + + page_type = infer_page_type(abs_to_rel(file_path)) + if not page_type or page_type == "landing": + sys.exit(0) + + required = get_required_sections(page_type) + if not required: + sys.exit(0) + + actual = [h.strip().lower() for h in SECTION_HEADING_RE.findall(content)] + missing = [s for s in required if s not in actual] + + if missing: + print(f"TEMPLATE WARNING ({page_type}):", file=sys.stderr) + print(f" Missing required sections: {', '.join(f'## {s.title()}' for s in missing)}", file=sys.stderr) + print(f" Template: .claude/templates/{page_type}.mdx", file=sys.stderr) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.claude/hooks/validate-frontmatter.py b/.claude/hooks/validate-frontmatter.py new file mode 100644 index 0000000..3c3b66c --- /dev/null +++ b/.claude/hooks/validate-frontmatter.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Hook: validate frontmatter on MDX files after write/edit. + +Blocks saves with missing title or description. +""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "evals")) +from _lib import ( + load_hook_input, parse_frontmatter, is_placeholder, + MIN_DESCRIPTION_LENGTH, +) + + +def main(): + result = load_hook_input() + if result is None: + sys.exit(0) + + _, content = result + fm = parse_frontmatter(content) + errors = [] + + if not fm.get("title"): + errors.append("Missing required frontmatter field: title") + + desc = fm.get("description", "") + if not desc: + errors.append("Missing required frontmatter field: description") + elif len(desc) < MIN_DESCRIPTION_LENGTH: + errors.append(f"Description too short ({len(desc)} chars, need {MIN_DESCRIPTION_LENGTH}+)") + elif is_placeholder(desc): + errors.append(f"Placeholder description detected: '{desc}'") + + if errors: + print("FRONTMATTER VALIDATION FAILED:", file=sys.stderr) + for e in errors: + print(f" ✗ {e}", file=sys.stderr) + sys.exit(1) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..efff9af --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,23 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "python3 .claude/hooks/validate-frontmatter.py" + }, + { + "type": "command", + "command": "python3 .claude/hooks/validate-body-sections.py" + }, + { + "type": "command", + "command": "python3 .claude/hooks/check-description.py" + } + ] + } + ] + } +} diff --git a/.claude/skills/evaluate.md b/.claude/skills/evaluate.md new file mode 100644 index 0000000..b2c499e --- /dev/null +++ b/.claude/skills/evaluate.md @@ -0,0 +1,58 @@ +--- +name: evaluate +description: Run documentation quality evals. Use when asked to check quality, run evals, or validate docs. Also use after completing a batch of content work to verify quality. +--- + +# Evaluate documentation quality + +Run the eval suite to measure documentation health. + +## Usage + +- `/evaluate` — run all evals and report scores +- `/evaluate frontmatter` — run a specific eval +- `/evaluate --threshold 90` — override pass threshold + +## Available evals + +| Eval | What it checks | Default threshold | +|---|---|---| +| `frontmatter-completeness` | title + description present | 95% | +| `content-depth` | Stub detection (TODO-only pages) | 50% | +| `description-quality` | Non-placeholder, >= 20 chars | 95% | +| `orphan-detector` | Every .mdx in docs.json | 90% | +| `navigation-coverage` | Every docs.json entry has a .mdx file | 90% | +| `openapi-completeness` | No TODO stubs in OpenAPI specs | 50% | + +## Instructions + +When the user runs `/evaluate` with no arguments, run ALL evals: + +```bash +for eval in frontmatter-completeness content-depth description-quality orphan-detector navigation-coverage openapi-completeness; do + python3 .claude/evals/$eval.py --json 2>&1 + echo "---" +done +``` + +Parse each result JSON and present a summary table: + +| Eval | Score | Status | +|---|---|---| +| name | XX.X% | PASS/FAIL | + +If a specific eval name is given, run only that one: + +```bash +python3 .claude/evals/{eval-name}.py --json 2>&1 +``` + +Show detailed issues for any eval scoring below 80%. + +After showing results, suggest the highest-impact fix: which eval has the worst score and what specific files need attention. + +When `--threshold` is passed, override the default threshold for all evals. + +## After content work + +When you've just finished writing or editing multiple pages, proactively suggest running `/evaluate` to verify quality hasn't regressed. diff --git a/.claude/templates/errors-catalog.mdx b/.claude/templates/errors-catalog.mdx new file mode 100644 index 0000000..4985de7 --- /dev/null +++ b/.claude/templates/errors-catalog.mdx @@ -0,0 +1,12 @@ +--- +title: "" +description: "" +--- + +## Overview + +## Shared errors + +## Account errors + +## Payment errors diff --git a/.claude/templates/guide.mdx b/.claude/templates/guide.mdx new file mode 100644 index 0000000..76d27b8 --- /dev/null +++ b/.claude/templates/guide.mdx @@ -0,0 +1,10 @@ +--- +title: "" +description: "" +--- + +## Overview + +## Details + +## Related diff --git a/.claude/templates/journey.mdx b/.claude/templates/journey.mdx new file mode 100644 index 0000000..2828463 --- /dev/null +++ b/.claude/templates/journey.mdx @@ -0,0 +1,12 @@ +--- +title: "" +description: "" +--- + +## Overview + +## Prerequisites + +## Steps + +## What happens next diff --git a/.claude/templates/principle.mdx b/.claude/templates/principle.mdx new file mode 100644 index 0000000..43ddd8d --- /dev/null +++ b/.claude/templates/principle.mdx @@ -0,0 +1,10 @@ +--- +title: "" +description: "" +--- + +## Overview + +## How it works + +## Examples diff --git a/.claude/templates/quickstart.mdx b/.claude/templates/quickstart.mdx new file mode 100644 index 0000000..d2fa72b --- /dev/null +++ b/.claude/templates/quickstart.mdx @@ -0,0 +1,8 @@ +--- +title: "" +description: "" +--- + +## Prerequisites + +## Steps diff --git a/.claude/templates/reference-overview.mdx b/.claude/templates/reference-overview.mdx new file mode 100644 index 0000000..b36a178 --- /dev/null +++ b/.claude/templates/reference-overview.mdx @@ -0,0 +1,9 @@ +--- +title: "" +description: "" +sidebarTitle: "" +--- + +## Overview + +## Key concepts diff --git a/.claude/templates/webhook-events.mdx b/.claude/templates/webhook-events.mdx new file mode 100644 index 0000000..8331709 --- /dev/null +++ b/.claude/templates/webhook-events.mdx @@ -0,0 +1,10 @@ +--- +title: "" +description: "" +--- + +## Overview + +## Account events + +## Payment events diff --git a/.claude/templates/webhook-overview.mdx b/.claude/templates/webhook-overview.mdx new file mode 100644 index 0000000..076316c --- /dev/null +++ b/.claude/templates/webhook-overview.mdx @@ -0,0 +1,12 @@ +--- +title: "" +description: "" +--- + +## Overview + +## Setup + +## Signatures + +## Retry policy diff --git a/.github/workflows/docs-quality.yml b/.github/workflows/docs-quality.yml new file mode 100644 index 0000000..2dbfc31 --- /dev/null +++ b/.github/workflows/docs-quality.yml @@ -0,0 +1,68 @@ +name: Documentation Quality + +on: + pull_request: + branches: [main] + +jobs: + mint-build: + name: Mintlify build validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npm i -g mint + - name: Validate build + run: mint validate + - name: Check broken links + run: mint broken-links + - name: Accessibility check + run: mint a11y + + frontmatter: + name: Frontmatter completeness + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Run eval + run: python3 .claude/evals/frontmatter-completeness.py --threshold 95 + + descriptions: + name: Description quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Run eval + run: python3 .claude/evals/description-quality.py --threshold 95 + + navigation: + name: Navigation coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Orphan detection + run: python3 .claude/evals/orphan-detector.py --threshold 90 + - name: Navigation coverage + run: python3 .claude/evals/navigation-coverage.py --threshold 90 + + markdown-format: + name: Markdown linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Lint changed files + run: npx markdownlint-cli2 "**/*.mdx" --config .markdownlint.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..4723472 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,13 @@ +{ + "default": true, + "MD013": false, + "MD033": false, + "MD037": false, + "MD041": false, + "MD024": { + "siblings_only": true + }, + "MD025": { + "front_matter_title": "" + } +} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index cebd973..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,33 +0,0 @@ -> **First-time setup**: Customize this file for your project. Prompt the user to customize this file for their project. -> For Mintlify product knowledge (components, configuration, writing standards), -> install the Mintlify skill: `npx skills add https://mintlify.com/docs` - -# Documentation project instructions - -## About this project - -- This is a documentation site built on [Mintlify](https://mintlify.com) -- Pages are MDX files with YAML frontmatter -- Configuration lives in `docs.json` -- Run `mint dev` to preview locally -- Run `mint broken-links` to check links - -## Terminology - -{/* Add product-specific terms and preferred usage */} -{/* Example: Use "workspace" not "project", "member" not "user" */} - -## Style preferences - -{/* Add any project-specific style rules below */} - -- Use active voice and second person ("you") -- Keep sentences concise — one idea per sentence -- Use sentence case for headings -- Bold for UI elements: Click **Settings** -- Code formatting for file names, commands, paths, and code references - -## Content boundaries - -{/* Define what should and shouldn't be documented */} -{/* Example: Don't document internal admin features */} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..11c2209 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,127 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +Trace Finance's public developer documentation for the FX platform, built on [Mintlify](https://mintlify.com). Partners ("customers") integrate with two services — **FX Account** (multi-currency account management) and **FX Payment** (deposits, withdrawals, swaps, beneficiaries) — documented here as one unified API surface. + +Content is public. Internal class names, backoffice endpoints, dashboard endpoints, and ADRs never appear here. + +## Commands + +- `mint dev` — preview at `http://localhost:3000` (run from repo root) +- `mint build` — strict build validation + OpenAPI spec validation +- `mint broken-links` — check internal links +- `mint accessibility` — check alt text and color contrast +- `mint update` — upgrade CLI if `mint dev` misbehaves +- Install CLI: `npm i -g mint` + +Validation = `mint build` passes + `mint broken-links` passes + `mint accessibility` passes. + +## Architecture + +### Configuration + +**`docs.json`** is the source of truth for navigation, theming, and OpenAPI wiring. Currently one tab: **Guides**. The **API Reference** tab will be added when OpenAPI schemas are fleshed out. Every page must be listed in `docs.json` or it won't appear in the sidebar. + +### Content (MDX) + +Pages are MDX with YAML frontmatter (`title` + `description` required). Mintlify components (``, ``, ``, etc.) are available without imports. + +| Folder | Content | +|---|---| +| `guides/` | Authentication, environments, API principles (versioning, idempotency, pagination, money, errors) | +| `journeys/` | End-to-end flows: open accounts, deposit, withdraw, swap | +| `webhooks/` | Webhook setup and event catalog | +| `snippets/` | Reusable MDX fragments imported into other pages | +| `apis/` | OpenAPI specs (scaffold) — `fx-account/openapi.yml` and `fx-payment/openapi.yml` | + +### API Reference (OpenAPI) — deferred + +OpenAPI specs live in `apis/` but are not yet wired into `docs.json` navigation. When schemas are ready, add the `openapi` key and an API Reference tab to `docs.json`. Endpoint pages auto-generate from `apis/{service}/openapi.yml`. Only `/api` channel endpoints are documented. `/dashboard` and `/admin` are internal. + +## Terminology + +- **Customer** = the company integrating with Trace FX. +- **Account owner** = the person whose account is managed (maps to `account.owner`). +- Never use "partner" or "client." + +## Key rules (full rulebook in CONTRIBUTING.md) + +- Internal links: root-relative, no extension (`/guides/authentication`). +- No internal class names in public content (`AmountV2` → "amount object"). +- Money: minor units as integers (`{ "value": 500000, "asset": "BRL" }`). +- Code blocks always declare language. +- Sentence case headings; no marketing adjectives; no filler. +- OpenAPI specs live here — when service teams change `/api` endpoints, they PR the spec update here too. + +## When adding a page + +1. Create `.mdx` with `title` + `description` frontmatter. +2. Add path to `docs.json` navigation. +3. `mint dev` to verify rendering. +4. `mint broken-links` before committing. + +## Quality system + +This repo has a dual-layer quality system adapted from the Trace knowledge base vault. + +### Hooks (real-time, on every write/edit) + +Registered in `.claude/settings.json` as PostToolUse hooks: + +| Hook | Behavior | +|---|---| +| `validate-frontmatter.py` | **Blocks** saves with missing `title` or bad `description` | +| `validate-body-sections.py` | **Warns** about missing required sections per page type | +| `check-description.py` | **Warns** about marketing language in descriptions | + +### Evals (run via `/evaluate` skill) + +| Eval | Checks | CI Threshold | +|---|---|---| +| `frontmatter-completeness` | title + description present | 95% | +| `content-depth` | Stub detection (TODO-only pages) | 50% | +| `description-quality` | Non-placeholder descriptions | 95% | +| `orphan-detector` | Every .mdx in docs.json | 90% | +| `navigation-coverage` | Every docs.json entry has .mdx | 90% | +| `openapi-completeness` | No TODO stubs in OpenAPI specs | 50% (deferred from CI until API Reference tab is added) | + +Run a single eval: `python3 .claude/evals/{name}.py --json` +Run all: use the `/evaluate` skill. + +### Templates + +`.claude/templates/{page-type}.mdx` define required `##` sections per page type. The `validate-body-sections` hook checks new content against these. + +Page types are inferred from file paths: +- `guides/principles/*.mdx` → `principle` (Overview, How it works, Examples) +- `guides/*.mdx` → `guide` (Overview, Details, Related) +- `journeys/*.mdx` → `journey` (Overview, Prerequisites, Steps, What happens next) +- `reference/*-overview.mdx` → `reference-overview` (Overview, Key concepts) +- `webhooks/overview.mdx` → `webhook-overview` (Overview, Setup, Signatures, Retry policy) + +### Reusable snippets + +`snippets/` contains shared MDX blocks. Import them in any page: + +```mdx +import SandboxNote from '/snippets/sandbox-note.mdx'; + +``` + +Available: `auth-header`, `sandbox-note`, `idempotency-note`, `pagination-response`, `error-response`, `money-format`, `version-header`. + +### CI (GitHub Actions) + +`.github/workflows/docs-quality.yml` runs on every PR: `mint build`, `mint broken-links`, `mint accessibility`, plus all blocking evals with score thresholds. + +## Source services + +Internal docs for reference (not published, but useful for authoring): + +- **fx-account**: `../fx-account/docs/` — domain model, HTTP contracts, journeys, events, ADRs +- **fx-payment**: `../fx-payment/docs/` — domain model, HTTP contracts, journeys, events, ADRs + +These contain detailed specs for every endpoint, event payload, and business rule. Use them as source material when writing guides and filling OpenAPI schemas. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8863ee4..5425868 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,34 +1,150 @@ -> **Customize this file**: Tailor this template to your project by noting specific contribution types you're looking for, adding a Code of Conduct, or adjusting the writing guidelines to match your style. +# Contributing to Trace Finance Developer Docs -# Contribute to the documentation +Rules and conventions for anyone editing this documentation site. -Thank you for your interest in contributing to our documentation! This guide will help you get started. +## Audience -## How to contribute +These docs are **public** and serve **customers** — the companies integrating with Trace FX. Do not publish internal implementation details, backoffice endpoints, or dashboard-specific content. -### Option 1: Edit directly on GitHub +## Terminology -1. Navigate to the page you want to edit -2. Click the "Edit this file" button (the pencil icon) -3. Make your changes and submit a pull request +| Term | Meaning | Usage | +|---|---|---| +| **Customer** | The company integrating with Trace FX | "As a customer, you authenticate using your API credentials." | +| **Account owner** | The person or entity whose account is being managed (maps to `account.owner` in the API) | "Create an account for an account owner." | -### Option 2: Local development +Never use "customer" to mean the account owner. Never use "partner" or "client" — use "customer." -1. Fork and clone this repository -2. Install the Mintlify CLI: `npm i -g mint` -3. Create a branch for your changes -4. Make changes -5. Navigate to the docs directory and run `mint dev` -6. Preview your changes at `http://localhost:3000` -7. Commit your changes and submit a pull request +## Content ownership -For more details on local development, see our [development guide](development.mdx). +| Content | Location | Owner | When to update | +|---|---|---|---| +| Endpoint reference (OpenAPI) | `apis/{service}/openapi.yml` | Service team | Every `/api` endpoint change | +| Everything else (MDX) | `guides/`, `journeys/`, `webhooks/`, `reference/`, `snippets/` | Whoever writes docs | Product or conceptual changes | -## Writing guidelines +## Drift prevention -- **Use active voice**: "Run the command" not "The command should be run" -- **Address the reader directly**: Use "you" instead of "the user" -- **Keep sentences concise**: Aim for one idea per sentence -- **Lead with the goal**: Start instructions with what the user wants to accomplish -- **Use consistent terminology**: Don't alternate between synonyms for the same concept -- **Include examples**: Show, don't just tell +OpenAPI specs live in this repo, not in the service repos. To prevent drift: + +1. **PR checklist in service repos**: if a PR changes an `/api` endpoint, the author opens a follow-up PR here updating `apis/{service}/openapi.yml`. +2. **CODEOWNERS**: `apis/fx-account/openapi.yml` and `apis/fx-payment/openapi.yml` require review from the respective backend team. +3. **Quarterly audit**: verify that deployed endpoints still match the committed specs. + +## Scope: `/api` channel only + +Both services expose three channels (`/api`, `/dashboard`, `/admin`). Only `/api` endpoints appear in these docs. Strip `/dashboard` and `/admin` paths from OpenAPI specs before committing. + +## File naming + +- Kebab-case: `open-brl-account.mdx`, not `openBrlAccount.mdx`. +- No date prefixes or version numbers: `deposit.mdx`, not `01-deposit.mdx`. +- The filename becomes the URL path — keep it readable. + +## Frontmatter + +Every MDX file requires `title` and `description`: + +```yaml +--- +title: "Authenticate requests" +description: "How to obtain and use Auth0 JWTs to call Trace FX APIs." +--- +``` + +Optional fields: +- `sidebarTitle`: when `title` would wrap in the sidebar. +- `icon`: for top-level group landing pages only. +- `tag: "BETA"`: for unreleased endpoints or features. + +## Internal links + +Root-relative, no file extension: + +```mdx +[see authentication](/guides/authentication) +``` + +Never use relative paths (`../`) or absolute external URLs for internal pages. + +## Voice and style + +- Second-person ("you"), active voice. +- Sentence case for headings. +- Bold for UI elements (**Dashboard**). +- `code` formatting for filenames, commands, headers (`X-Idempotency-Key`), and endpoint paths (`POST /accounts`). +- No marketing adjectives (*powerful, seamless, robust*). +- No filler phrases (*in order to, it's important to note*). +- No editorializing (*simply, just, obviously*). +- Internal class names (`AmountV2`, `ApplicationException`, `DefaultClaims`) **never appear** in public content. Use the field name as customers see it: "amount object", "error response." + +## Components + +Use Mintlify built-in components. Do not create custom components. + +| Need | Use | +|---|---| +| Step-by-step instructions | `` | +| Mutually exclusive examples | `` | +| Optional deep detail | `` / `` | +| Multi-language code | `` | +| Callouts | ``, ``, ``, ``, `` | +| Cross-page navigation | `` | +| API parameter (MDX-only reference) | `` | + +## Code examples + +- Use realistic values: actual currency codes (`BRL`, `USD`), realistic UUIDs, sandbox base URL (`https://faas.sandbox.tracefinance.io`). +- Every code block must declare its language: ` ```json `, ` ```bash `, etc. +- Money uses minor units: `{ "value": 500000, "asset": "BRL" }` not `{ "amount": "5.00 BRL" }`. +- No `foo`, `bar`, `test123`, or placeholder values. + +## Reusable content + +Shared fragments live in `snippets/`. Import them in MDX: + +```mdx +import AuthHeader from '/snippets/auth-header.mdx'; + + +``` + +Only create a snippet when the exact same content appears on multiple pages. Do not snippet content that varies between pages. + +## Adding a new endpoint + +1. Add the operation to `apis/{service}/openapi.yml`. +2. Add the operation path to the relevant group in `docs.json` (e.g., `"POST /api/deposits"`). +3. Run `mint dev` — verify the endpoint renders with a playground. +4. Run `mint broken-links`. + +## Adding a new guide page + +1. Create the MDX file at the appropriate path (e.g., `guides/topic.mdx`). +2. Add the path (without `.mdx`) to the appropriate group in `docs.json`. +3. Include `title` and `description` frontmatter. +4. Cross-link from at least one existing page — orphan pages are hard to find. +5. Run `mint dev` and `mint broken-links`. + +## Validation before merge + +Every PR to `main` must pass: + +- `mint dev` renders changed pages without errors. +- `mint broken-links` passes. +- `mint validate` passes. +- OpenAPI changes validate as OpenAPI 3.x. + +## `.mintignore` + +Drafts go in `drafts/` or use `*.draft.mdx`. Use `.mintignore` to exclude files from builds entirely. Do not rely on "not in docs.json" — Mintlify can still index unlisted pages for search. + +## Images + +Store in `/images/{topic}/`. Always include descriptive alt text that says what the image *conveys*, not what it *is*. + +Provide light and dark variants when images have white backgrounds: + +```mdx +... +... +``` diff --git a/ai-tools/claude-code.mdx b/ai-tools/claude-code.mdx deleted file mode 100644 index a59ca25..0000000 --- a/ai-tools/claude-code.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Claude Code setup" -description: "Configure Claude Code for your documentation workflow" -icon: "asterisk" ---- - -Set up Claude Code to help you write and maintain your Mintlify documentation. - -## Prerequisites - -- Active Claude subscription (Pro, Max, or API access) - -## Setup - - - - ```bash - npm install -g @anthropic-ai/claude-code - ``` - - - - In your docs directory, run: - - ```bash - npx skills add https://mintlify.com/docs - ``` - - This gives Claude Code Mintlify's component reference, writing standards, - and workflow guidance. - - - - Edit `AGENTS.md` in your project root to add project-specific terminology, - style preferences, and content boundaries. - - - - ```bash - claude - ``` - - diff --git a/ai-tools/cursor.mdx b/ai-tools/cursor.mdx deleted file mode 100644 index 80bab56..0000000 --- a/ai-tools/cursor.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Cursor setup" -description: "Configure Cursor for your documentation workflow" -icon: "arrow-pointer" ---- - -Set up Cursor to help you write and maintain your Mintlify documentation. - -## Prerequisites - -- Cursor editor installed - -## Setup - - - - Open the root of your documentation repository where `docs.json` is located. - - - - In the integrated terminal, run: - - ```bash - npx skills add https://mintlify.com/docs - ``` - - This gives Cursor Mintlify's component reference, writing standards, - and workflow guidance. - - - - Edit `AGENTS.md` in your project root to add project-specific terminology, - style preferences, and content boundaries. - - - - Open a file and use Cursor's AI features to draft and edit documentation. - - diff --git a/ai-tools/windsurf.mdx b/ai-tools/windsurf.mdx deleted file mode 100644 index 0776cca..0000000 --- a/ai-tools/windsurf.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Windsurf setup" -description: "Configure Windsurf for your documentation workflow" -icon: "water" ---- - -Set up Windsurf's Cascade AI assistant to help you write and maintain your Mintlify documentation. - -## Prerequisites - -- Windsurf editor installed - -## Setup - - - - Open the root of your documentation repository where `docs.json` is located. - - - - In the integrated terminal, run: - - ```bash - npx skills add https://mintlify.com/docs - ``` - - This gives Windsurf Mintlify's component reference, writing standards, - and workflow guidance. - - - - Edit `AGENTS.md` in your project root to add project-specific terminology, - style preferences, and content boundaries. - - - - Open a file and use Cascade to draft and edit documentation. - - diff --git a/api-reference/endpoint/create.mdx b/api-reference/endpoint/create.mdx deleted file mode 100644 index 5689f1b..0000000 --- a/api-reference/endpoint/create.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Create Plant' -openapi: 'POST /plants' ---- diff --git a/api-reference/endpoint/delete.mdx b/api-reference/endpoint/delete.mdx deleted file mode 100644 index 657dfc8..0000000 --- a/api-reference/endpoint/delete.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Delete Plant' -openapi: 'DELETE /plants/{id}' ---- diff --git a/api-reference/endpoint/get.mdx b/api-reference/endpoint/get.mdx deleted file mode 100644 index 56aa09e..0000000 --- a/api-reference/endpoint/get.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Get Plants' -openapi: 'GET /plants' ---- diff --git a/api-reference/endpoint/webhook.mdx b/api-reference/endpoint/webhook.mdx deleted file mode 100644 index 3291340..0000000 --- a/api-reference/endpoint/webhook.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'New Plant' -openapi: 'WEBHOOK /plant/webhook' ---- diff --git a/api-reference/introduction.mdx b/api-reference/introduction.mdx deleted file mode 100644 index c835b78..0000000 --- a/api-reference/introduction.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: 'Introduction' -description: 'Example section for showcasing API endpoints' ---- - - - If you're not looking to build API reference documentation, you can delete - this section by removing the api-reference folder. - - -## Welcome - -There are two ways to build API documentation: [OpenAPI](https://mintlify.com/docs/api-playground/openapi/setup) and [MDX components](https://mintlify.com/docs/api-playground/mdx/configuration). For the starter kit, we are using the following OpenAPI specification. - - - View the OpenAPI specification file - - -## Authentication - -All API endpoints are authenticated using Bearer tokens and picked up from the specification file. - -```json -"security": [ - { - "bearerAuth": [] - } -] -``` diff --git a/api-reference/openapi.json b/api-reference/openapi.json deleted file mode 100644 index da5326e..0000000 --- a/api-reference/openapi.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "OpenAPI Plant Store", - "description": "A sample API that uses a plant store as an example to demonstrate features in the OpenAPI specification", - "license": { - "name": "MIT" - }, - "version": "1.0.0" - }, - "servers": [ - { - "url": "http://sandbox.mintlify.com" - } - ], - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/plants": { - "get": { - "description": "Returns all plants from the system that the user has access to", - "parameters": [ - { - "name": "limit", - "in": "query", - "description": "The maximum number of results to return", - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Plant response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Plant" - } - } - } - } - }, - "400": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "description": "Creates a new plant in the store", - "requestBody": { - "description": "Plant to add to the store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewPlant" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "plant response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Plant" - } - } - } - }, - "400": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/plants/{id}": { - "delete": { - "description": "Deletes a single plant based on the ID supplied", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of plant to delete", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "204": { - "description": "Plant deleted", - "content": {} - }, - "400": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - } - }, - "webhooks": { - "/plant/webhook": { - "post": { - "description": "Information about a new plant added to the store", - "requestBody": { - "description": "Plant added to the store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewPlant" - } - } - } - }, - "responses": { - "200": { - "description": "Return a 200 status to indicate that the data was received successfully" - } - } - } - } - }, - "components": { - "schemas": { - "Plant": { - "required": [ - "name" - ], - "type": "object", - "properties": { - "name": { - "description": "The name of the plant", - "type": "string" - }, - "tag": { - "description": "Tag to specify the type", - "type": "string" - } - } - }, - "NewPlant": { - "allOf": [ - { - "$ref": "#/components/schemas/Plant" - }, - { - "required": [ - "id" - ], - "type": "object", - "properties": { - "id": { - "description": "Identification number of the plant", - "type": "integer", - "format": "int64" - } - } - } - ] - }, - "Error": { - "required": [ - "error", - "message" - ], - "type": "object", - "properties": { - "error": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } - } - } - }, - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer" - } - } - } -} \ No newline at end of file diff --git a/apis/fx-account/openapi.yml b/apis/fx-account/openapi.yml new file mode 100644 index 0000000..32b0abb --- /dev/null +++ b/apis/fx-account/openapi.yml @@ -0,0 +1,187 @@ +openapi: 3.1.1 +info: + title: Trace FX Accounts API + version: 1.0.0 + description: API for managing multi-currency accounts on the Trace FX platform. +servers: + - url: https://faas.sandbox.tracefinance.io/account + description: Sandbox + - url: https://faas.tracefinance.io/account + description: Production +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: "Auth0 JWT token. Include as `Authorization: Bearer `." + schemas: + AddressResponse: + type: object + properties: + street: + type: string + example: "Rua das Flores" + number: + type: string + example: "100" + district: + type: string + example: "Centro" + city: + type: string + example: "São Paulo" + state: + type: string + example: "SP" + country: + type: string + example: "BR" + zipCode: + type: string + example: "01000-000" + complement: + type: string + nullable: true + example: "Apto 101" + required: + - street + - number + - district + - city + - state + - country + - zipCode + OwnerResponse: + type: object + description: The account owner. + properties: + id: + type: string + format: uuid + example: "f47ac10b-58cc-4372-a567-0e02b2c3d479" + name: + type: string + example: "ACME Ltda" + taxId: + type: string + example: "12345678000190" + address: + $ref: '#/components/schemas/AddressResponse' + required: + - id + - name + - taxId + - address + AccountResponse: + type: object + properties: + id: + type: string + format: uuid + example: "a1b2c3d4-5e6f-7890-abcd-ef1234567890" + externalId: + type: string + nullable: true + description: Your own identifier for this account, if provided at creation. + example: "ext-123" + owner: + $ref: '#/components/schemas/OwnerResponse' + createdAt: + type: string + format: date-time + example: "2025-01-15T10:30:00Z" + updatedAt: + type: string + format: date-time + example: "2025-01-15T10:30:00Z" + required: + - id + - owner + - createdAt + - updatedAt + ErrorResponse: + type: object + properties: + code: + type: string + example: "RESOURCE_NOT_FOUND" + message: + type: string + example: "The requested resource was not found" + details: + type: object + additionalProperties: true + example: {} + required: + - code + - message +paths: + /api/accounts/{accountId}: + get: + operationId: getAccountById + tags: + - Accounts + summary: Get an account + description: Retrieve the details of an account by its ID. + parameters: + - name: accountId + in: path + required: true + description: UUID of the account. + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + description: API version. Omit to use the default version. + schema: + type: string + responses: + "200": + description: Account details. + content: + application/json: + schema: + $ref: '#/components/schemas/AccountResponse' + examples: + success: + value: + id: "a1b2c3d4-5e6f-7890-abcd-ef1234567890" + externalId: "ext-123" + owner: + id: "f47ac10b-58cc-4372-a567-0e02b2c3d479" + name: "ACME Ltda" + taxId: "12345678000190" + address: + street: "Rua das Flores" + number: "100" + district: "Centro" + city: "São Paulo" + state: "SP" + country: "BR" + zipCode: "01000-000" + complement: "Apto 101" + createdAt: "2025-01-15T10:30:00Z" + updatedAt: "2025-01-15T10:30:00Z" + "400": + description: Invalid request data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + "401": + description: Missing or invalid authentication token. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + "404": + description: Account not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' diff --git a/apis/fx-payment/openapi.yml b/apis/fx-payment/openapi.yml new file mode 100644 index 0000000..0c19aaf --- /dev/null +++ b/apis/fx-payment/openapi.yml @@ -0,0 +1,365 @@ +openapi: 3.1.1 +info: + title: Trace FX Payments API + version: 1.0.0 + description: | + API for deposits, withdrawals, swaps, and beneficiary management + on the Trace FX platform. + + **This spec is a scaffold — endpoints require request/response schemas.** +servers: + - url: https://faas.sandbox.tracefinance.io/payment + description: Sandbox + - url: https://faas.tracefinance.io/payment + description: Production +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: "Auth0 JWT token. Include as `Authorization: Bearer `." + schemas: + ErrorResponse: + type: object + properties: + code: + type: string + example: "RESOURCE_NOT_FOUND" + message: + type: string + example: "The requested resource was not found" + details: + type: object + additionalProperties: true + example: {} + required: + - code + - message +paths: + # --- Operations: Deposits --- + /api/deposits: + post: + operationId: createDeposit + tags: + - Operations + summary: Create a deposit + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: X-Idempotency-Key + in: header + required: true + schema: + type: string + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "201": + description: Deposit created. + "400": + description: Invalid request data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + # --- Operations: Withdrawals --- + /api/withdrawals: + post: + operationId: createWithdrawal + tags: + - Operations + summary: Create a withdrawal + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: X-Idempotency-Key + in: header + required: true + schema: + type: string + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "201": + description: Withdrawal created. + "400": + description: Invalid request data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + # --- Operations: Swaps --- + /api/swaps: + post: + operationId: createSwap + tags: + - Operations + summary: Create a swap + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: X-Idempotency-Key + in: header + required: true + schema: + type: string + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "201": + description: Swap created. + "400": + description: Invalid request data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + # --- Operations: Read --- + /api/operations/{operationId}: + get: + operationId: getOperation + tags: + - Operations + summary: Get an operation + description: "TODO: Add response schema from fx-payment docs." + parameters: + - name: operationId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "200": + description: Operation details. + "404": + description: Operation not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/operations: + get: + operationId: listOperations + tags: + - Operations + summary: List operations + description: "TODO: Add query parameters and response schema from fx-payment docs." + parameters: + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "200": + description: Paginated list of operations. + + # --- Beneficiaries --- + /api/beneficiaries: + post: + operationId: createBeneficiary + tags: + - Beneficiaries + summary: Create a beneficiary + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: X-Idempotency-Key + in: header + required: true + schema: + type: string + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "201": + description: Beneficiary created. + "400": + description: Invalid request data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + get: + operationId: listBeneficiaries + tags: + - Beneficiaries + summary: List beneficiaries + description: "TODO: Add query parameters and response schema from fx-payment docs." + parameters: + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "200": + description: Paginated list of beneficiaries. + + /api/beneficiaries/{beneficiaryId}: + get: + operationId: getBeneficiary + tags: + - Beneficiaries + summary: Get a beneficiary + description: "TODO: Add response schema from fx-payment docs." + parameters: + - name: beneficiaryId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "200": + description: Beneficiary details. + "404": + description: Beneficiary not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + operationId: updateBeneficiary + tags: + - Beneficiaries + summary: Update a beneficiary + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: beneficiaryId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "200": + description: Beneficiary updated. + "404": + description: Beneficiary not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + operationId: deleteBeneficiary + tags: + - Beneficiaries + summary: Delete a beneficiary + description: "TODO: Add response details from fx-payment docs." + parameters: + - name: beneficiaryId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "204": + description: Beneficiary deleted. + "404": + description: Beneficiary not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/beneficiaries/{beneficiaryId}/payment-instructions: + post: + operationId: addPaymentInstruction + tags: + - Beneficiaries + summary: Add a payment instruction + description: "TODO: Add request/response schemas from fx-payment docs." + parameters: + - name: beneficiaryId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "201": + description: Payment instruction added. + "404": + description: Beneficiary not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /api/beneficiaries/{beneficiaryId}/payment-instructions/{paymentInstructionId}: + delete: + operationId: removePaymentInstruction + tags: + - Beneficiaries + summary: Remove a payment instruction + description: "TODO: Add response details from fx-payment docs." + parameters: + - name: beneficiaryId + in: path + required: true + schema: + type: string + format: uuid + - name: paymentInstructionId + in: path + required: true + schema: + type: string + format: uuid + - name: X-Trace-Version + in: header + required: false + schema: + type: string + responses: + "204": + description: Payment instruction removed. + "404": + description: Beneficiary or payment instruction not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' diff --git a/development.mdx b/development.mdx deleted file mode 100644 index ac633ba..0000000 --- a/development.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: 'Development' -description: 'Preview changes locally to update your docs' ---- - - - **Prerequisites**: - - Node.js version 19 or higher - - A docs repository with a `docs.json` file - - -Follow these steps to install and run Mintlify on your operating system. - - - - -```bash -npm i -g mint -``` - - - - -Navigate to your docs directory where your `docs.json` file is located, and run the following command: - -```bash -mint dev -``` - -A local preview of your documentation will be available at `http://localhost:3000`. - - - - -## Custom ports - -By default, Mintlify uses port 3000. You can customize the port Mintlify runs on by using the `--port` flag. For example, to run Mintlify on port 3333, use this command: - -```bash -mint dev --port 3333 -``` - -If you attempt to run Mintlify on a port that's already in use, it will use the next available port: - -```md -Port 3000 is already in use. Trying 3001 instead. -``` - -## Mintlify versions - -Please note that each CLI release is associated with a specific version of Mintlify. If your local preview does not align with the production version, please update the CLI: - -```bash -npm mint update -``` - -## Validating links - -The CLI can assist with validating links in your documentation. To identify any broken links, use the following command: - -```bash -mint broken-links -``` - -## Deployment - -If the deployment is successful, you should see the following: - - - Screenshot of a deployment confirmation message that says All checks have passed. - - -## Code formatting - -We suggest using extensions on your IDE to recognize and format MDX. If you're a VSCode user, consider the [MDX VSCode extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) for syntax highlighting, and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) for code formatting. - -## Troubleshooting - - - - - This may be due to an outdated version of node. Try the following: - 1. Remove the currently-installed version of the CLI: `npm remove -g mint` - 2. Upgrade to Node v19 or higher. - 3. Reinstall the CLI: `npm i -g mint` - - - - - Solution: Go to the root of your device and delete the `~/.mintlify` folder. Then run `mint dev` again. - - - -Curious about what changed in the latest CLI version? Check out the [CLI changelog](https://www.npmjs.com/package/mintlify?activeTab=versions). diff --git a/docs.json b/docs.json index 46b44cc..1b9f874 100644 --- a/docs.json +++ b/docs.json @@ -1,13 +1,17 @@ { "$schema": "https://mintlify.com/docs.json", "theme": "mint", - "name": "Mint Starter Kit", + "name": "Trace Finance Developer Docs", "colors": { - "primary": "#16A34A", + "primary": "#15803D", "light": "#07C983", "dark": "#15803D" }, "favicon": "/favicon.svg", + "logo": { + "light": "/logo/light.svg", + "dark": "/logo/dark.svg" + }, "navigation": { "tabs": [ { @@ -18,51 +22,35 @@ "pages": [ "index", "quickstart", - "development" + "guides/authentication", + "guides/environments" ] }, { - "group": "Customization", + "group": "API principles", "pages": [ - "essentials/settings", - "essentials/navigation" + "guides/principles/versioning", + "guides/principles/idempotency", + "guides/principles/pagination", + "guides/principles/money", + "guides/principles/errors" ] }, { - "group": "Writing content", + "group": "Journeys", "pages": [ - "essentials/markdown", - "essentials/code", - "essentials/images", - "essentials/reusable-snippets" + "journeys/open-brl-account", + "journeys/open-crypto-account", + "journeys/deposit", + "journeys/withdrawal", + "journeys/swap" ] }, { - "group": "AI tools", + "group": "Webhooks", "pages": [ - "ai-tools/cursor", - "ai-tools/claude-code", - "ai-tools/windsurf" - ] - } - ] - }, - { - "tab": "API reference", - "groups": [ - { - "group": "API documentation", - "pages": [ - "api-reference/introduction" - ] - }, - { - "group": "Endpoint examples", - "pages": [ - "api-reference/endpoint/get", - "api-reference/endpoint/create", - "api-reference/endpoint/delete", - "api-reference/endpoint/webhook" + "webhooks/overview", + "webhooks/events" ] } ] @@ -71,52 +59,44 @@ "global": { "anchors": [ { - "anchor": "Documentation", - "href": "https://mintlify.com/docs", - "icon": "book-open-cover" - }, - { - "anchor": "Blog", - "href": "https://mintlify.com/blog", - "icon": "newspaper" + "anchor": "API Status", + "href": "https://status.tracefinance.io", + "icon": "signal" } ] } }, - "logo": { - "light": "/logo/light.svg", - "dark": "/logo/dark.svg" - }, "navbar": { "links": [ { "label": "Support", - "href": "mailto:hi@mintlify.com" + "href": "mailto:support@tracefinance.io" } + ] + }, + "api": { + "baseUrl": [ + "https://faas.sandbox.tracefinance.io", + "https://faas.tracefinance.io" ], - "primary": { - "type": "button", - "label": "Dashboard", - "href": "https://dashboard.mintlify.com" + "auth": { + "method": "bearer" } }, - "contextual": { - "options": [ - "copy", - "view", - "chatgpt", - "claude", - "perplexity", - "mcp", - "cursor", - "vscode" - ] + "description": "API documentation for Trace Finance's multi-currency account and payment platform.", + "seo": { + "indexing": "navigable" + }, + "search": { + "placeholder": "Search docs..." + }, + "metadata": { + "lastUpdatedDate": true }, "footer": { "socials": { - "x": "https://x.com/mintlify", - "github": "https://github.com/mintlify", - "linkedin": "https://linkedin.com/company/mintlify" + "github": "https://github.com/tracefinance", + "linkedin": "https://linkedin.com/company/tracefinance" } } } diff --git a/essentials/code.mdx b/essentials/code.mdx deleted file mode 100644 index ae2abbf..0000000 --- a/essentials/code.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: 'Code blocks' -description: 'Display inline code and code blocks' -icon: 'code' ---- - -## Inline code - -To denote a `word` or `phrase` as code, enclose it in backticks (`). - -``` -To denote a `word` or `phrase` as code, enclose it in backticks (`). -``` - -## Code blocks - -Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language. - -```java HelloWorld.java -class HelloWorld { - public static void main(String[] args) { - System.out.println("Hello, World!"); - } -} -``` - -````md -```java HelloWorld.java -class HelloWorld { - public static void main(String[] args) { - System.out.println("Hello, World!"); - } -} -``` -```` diff --git a/essentials/images.mdx b/essentials/images.mdx deleted file mode 100644 index 1144eb2..0000000 --- a/essentials/images.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: 'Images and embeds' -description: 'Add image, video, and other HTML elements' -icon: 'image' ---- - - - -## Image - -### Using Markdown - -The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code - -```md -![title](/path/image.jpg) -``` - -Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed. - -### Using embeds - -To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images - -```html - -``` - -## Embeds and HTML elements - - - -
- - - -Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility. - - - -### iFrames - -Loads another HTML page within the document. Most commonly used for embedding videos. - -```html - -``` diff --git a/essentials/markdown.mdx b/essentials/markdown.mdx deleted file mode 100644 index a45c1d5..0000000 --- a/essentials/markdown.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: 'Markdown syntax' -description: 'Text, title, and styling in standard markdown' -icon: 'text-size' ---- - -## Titles - -Best used for section headers. - -```md -## Titles -``` - -### Subtitles - -Best used for subsection headers. - -```md -### Subtitles -``` - - - -Each **title** and **subtitle** creates an anchor and also shows up on the table of contents on the right. - - - -## Text formatting - -We support most markdown formatting. Simply add `**`, `_`, or `~` around text to format it. - -| Style | How to write it | Result | -| ------------- | ----------------- | --------------- | -| Bold | `**bold**` | **bold** | -| Italic | `_italic_` | _italic_ | -| Strikethrough | `~strikethrough~` | ~strikethrough~ | - -You can combine these. For example, write `**_bold and italic_**` to get **_bold and italic_** text. - -You need to use HTML to write superscript and subscript text. That is, add `` or `` around your text. - -| Text Size | How to write it | Result | -| ----------- | ------------------------ | ---------------------- | -| Superscript | `superscript` | superscript | -| Subscript | `subscript` | subscript | - -## Linking to pages - -You can add a link by wrapping text in `[]()`. You would write `[link to google](https://google.com)` to [link to google](https://google.com). - -Links to pages in your docs need to be root-relative. Basically, you should include the entire folder path. For example, `[link to text](/writing-content/text)` links to the page "Text" in our components section. - -Relative links like `[link to text](../text)` will open slower because we cannot optimize them as easily. - -## Blockquotes - -### Singleline - -To create a blockquote, add a `>` in front of a paragraph. - -> Dorothy followed her through many of the beautiful rooms in her castle. - -```md -> Dorothy followed her through many of the beautiful rooms in her castle. -``` - -### Multiline - -> Dorothy followed her through many of the beautiful rooms in her castle. -> -> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. - -```md -> Dorothy followed her through many of the beautiful rooms in her castle. -> -> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. -``` - -### LaTeX - -Mintlify supports [LaTeX](https://www.latex-project.org) through the Latex component. - -8 x (vk x H1 - H2) = (0,1) - -```md -8 x (vk x H1 - H2) = (0,1) -``` diff --git a/essentials/navigation.mdx b/essentials/navigation.mdx deleted file mode 100644 index 60adeff..0000000 --- a/essentials/navigation.mdx +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: 'Navigation' -description: 'The navigation field in docs.json defines the pages that go in the navigation menu' -icon: 'map' ---- - -The navigation menu is the list of links on every website. - -You will likely update `docs.json` every time you add a new page. Pages do not show up automatically. - -## Navigation syntax - -Our navigation syntax is recursive which means you can make nested navigation groups. You don't need to include `.mdx` in page names. - - - -```json Regular Navigation -"navigation": { - "tabs": [ - { - "tab": "Docs", - "groups": [ - { - "group": "Getting Started", - "pages": ["quickstart"] - } - ] - } - ] -} -``` - -```json Nested Navigation -"navigation": { - "tabs": [ - { - "tab": "Docs", - "groups": [ - { - "group": "Getting Started", - "pages": [ - "quickstart", - { - "group": "Nested Reference Pages", - "pages": ["nested-reference-page"] - } - ] - } - ] - } - ] -} -``` - - - -## Folders - -Simply put your MDX files in folders and update the paths in `docs.json`. - -For example, to have a page at `https://yoursite.com/your-folder/your-page` you would make a folder called `your-folder` containing an MDX file called `your-page.mdx`. - - - -You cannot use `api` for the name of a folder unless you nest it inside another folder. Mintlify uses Next.js which reserves the top-level `api` folder for internal server calls. A folder name such as `api-reference` would be accepted. - - - -```json Navigation With Folder -"navigation": { - "tabs": [ - { - "tab": "Docs", - "groups": [ - { - "group": "Group Name", - "pages": ["your-folder/your-page"] - } - ] - } - ] -} -``` - -## Hidden pages - -MDX files not included in `docs.json` will not show up in the sidebar but are accessible through the search bar and by linking directly to them. diff --git a/essentials/reusable-snippets.mdx b/essentials/reusable-snippets.mdx deleted file mode 100644 index 376e27b..0000000 --- a/essentials/reusable-snippets.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "Reusable snippets" -description: "Reusable, custom snippets to keep content in sync" -icon: "recycle" ---- - -import SnippetIntro from '/snippets/snippet-intro.mdx'; - - - -## Creating a custom snippet - -**Pre-condition**: You must create your snippet file in the `snippets` directory. - - - Any page in the `snippets` directory will be treated as a snippet and will not - be rendered into a standalone page. If you want to create a standalone page - from the snippet, import the snippet into another file and call it as a - component. - - -### Default export - -1. Add content to your snippet file that you want to re-use across multiple - locations. Optionally, you can add variables that can be filled in via props - when you import the snippet. - -```mdx snippets/my-snippet.mdx -Hello world! This is my content I want to reuse across pages. My keyword of the -day is {word}. -``` - - - The content that you want to reuse must be inside the `snippets` directory in - order for the import to work. - - -2. Import the snippet into your destination file. - -```mdx destination-file.mdx ---- -title: My title -description: My Description ---- - -import MySnippet from '/snippets/path/to/my-snippet.mdx'; - -## Header - -Lorem impsum dolor sit amet. - - -``` - -### Reusable variables - -1. Export a variable from your snippet file: - -```mdx snippets/path/to/custom-variables.mdx -export const myName = 'my name'; - -export const myObject = { fruit: 'strawberries' }; -``` - -2. Import the snippet from your destination file and use the variable: - -```mdx destination-file.mdx ---- -title: My title -description: My Description ---- - -import { myName, myObject } from '/snippets/path/to/custom-variables.mdx'; - -Hello, my name is {myName} and I like {myObject.fruit}. -``` - -### Reusable components - -1. Inside your snippet file, create a component that takes in props by exporting - your component in the form of an arrow function. - -```mdx snippets/custom-component.mdx -export const MyComponent = ({ title }) => ( -
-

{title}

-

... snippet content ...

-
-); -``` - - - MDX does not compile inside the body of an arrow function. Stick to HTML - syntax when you can or use a default export if you need to use MDX. - - -2. Import the snippet into your destination file and pass in the props - -```mdx destination-file.mdx ---- -title: My title -description: My Description ---- - -import { MyComponent } from '/snippets/custom-component.mdx'; - -Lorem ipsum dolor sit amet. - - -``` diff --git a/essentials/settings.mdx b/essentials/settings.mdx deleted file mode 100644 index 884de13..0000000 --- a/essentials/settings.mdx +++ /dev/null @@ -1,318 +0,0 @@ ---- -title: 'Global Settings' -description: 'Mintlify gives you complete control over the look and feel of your documentation using the docs.json file' -icon: 'gear' ---- - -Every Mintlify site needs a `docs.json` file with the core configuration settings. Learn more about the [properties](#properties) below. - -## Properties - - -Name of your project. Used for the global title. - -Example: `mintlify` - - - - - An array of groups with all the pages within that group - - - The name of the group. - - Example: `Settings` - - - - The relative paths to the markdown files that will serve as pages. - - Example: `["customization", "page"]` - - - - - - - - Path to logo image or object with path to "light" and "dark" mode logo images - - - Path to the logo in light mode - - - Path to the logo in dark mode - - - Where clicking on the logo links you to - - - - - - Path to the favicon image - - - - Hex color codes for your global theme - - - The primary color. Used for most often for highlighted content, section - headers, accents, in light mode - - - The primary color for dark mode. Used for most often for highlighted - content, section headers, accents, in dark mode - - - The primary color for important buttons - - - The color of the background in both light and dark mode - - - The hex color code of the background in light mode - - - The hex color code of the background in dark mode - - - - - - - - Array of `name`s and `url`s of links you want to include in the topbar - - - The name of the button. - - Example: `Contact us` - - - The url once you click on the button. Example: `https://mintlify.com/docs` - - - - - - - - - Link shows a button. GitHub shows the repo information at the url provided including the number of GitHub stars. - - - If `link`: What the button links to. - - If `github`: Link to the repository to load GitHub information from. - - - Text inside the button. Only required if `type` is a `link`. - - - - - - - Array of version names. Only use this if you want to show different versions - of docs with a dropdown in the navigation bar. - - - - An array of the anchors, includes the `icon`, `color`, and `url`. - - - The [Font Awesome](https://fontawesome.com/search?q=heart) icon used to feature the anchor. - - Example: `comments` - - - The name of the anchor label. - - Example: `Community` - - - The start of the URL that marks what pages go in the anchor. Generally, this is the name of the folder you put your pages in. - - - The hex color of the anchor icon background. Can also be a gradient if you pass an object with the properties `from` and `to` that are each a hex color. - - - Used if you want to hide an anchor until the correct docs version is selected. - - - Pass `true` if you want to hide the anchor until you directly link someone to docs inside it. - - - One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin" - - - - - - - Override the default configurations for the top-most anchor. - - - The name of the top-most anchor - - - Font Awesome icon. - - - One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin" - - - - - - An array of navigational tabs. - - - The name of the tab label. - - - The start of the URL that marks what pages go in the tab. Generally, this - is the name of the folder you put your pages in. - - - - - - Configuration for API settings. Learn more about API pages at [API Components](/api-playground/demo). - - - The base url for all API endpoints. If `baseUrl` is an array, it will enable for multiple base url - options that the user can toggle. - - - - - - The authentication strategy used for all API endpoints. - - - The name of the authentication parameter used in the API playground. - - If method is `basic`, the format should be `[usernameName]:[passwordName]` - - - The default value that's designed to be a prefix for the authentication input field. - - E.g. If an `inputPrefix` of `AuthKey` would inherit the default input result of the authentication field as `AuthKey`. - - - - - - Configurations for the API playground - - - - Whether the playground is showing, hidden, or only displaying the endpoint with no added user interactivity `simple` - - Learn more at the [playground guides](/api-playground/demo) - - - - - - Enabling this flag ensures that key ordering in OpenAPI pages matches the key ordering defined in the OpenAPI file. - - This behavior will soon be enabled by default, at which point this field will be deprecated. - - - - - - - A string or an array of strings of URL(s) or relative path(s) pointing to your - OpenAPI file. - - Examples: - - ```json Absolute - "openapi": "https://example.com/openapi.json" - ``` - ```json Relative - "openapi": "/openapi.json" - ``` - ```json Multiple - "openapi": ["https://example.com/openapi1.json", "/openapi2.json", "/openapi3.json"] - ``` - - - - - - An object of social media accounts where the key:property pair represents the social media platform and the account url. - - Example: - ```json - { - "x": "https://x.com/mintlify", - "website": "https://mintlify.com" - } - ``` - - - One of the following values `website`, `facebook`, `x`, `discord`, `slack`, `github`, `linkedin`, `instagram`, `hacker-news` - - Example: `x` - - - The URL to the social platform. - - Example: `https://x.com/mintlify` - - - - - - Configurations to enable feedback buttons - - - - Enables a button to allow users to suggest edits via pull requests - - - Enables a button to allow users to raise an issue about the documentation - - - - - - Customize the dark mode toggle. - - - Set if you always want to show light or dark mode for new users. When not - set, we default to the same mode as the user's operating system. - - - Set to true to hide the dark/light mode toggle. You can combine `isHidden` with `default` to force your docs to only use light or dark mode. For example: - - - ```json Only Dark Mode - "modeToggle": { - "default": "dark", - "isHidden": true - } - ``` - - ```json Only Light Mode - "modeToggle": { - "default": "light", - "isHidden": true - } - ``` - - - - - - - - - A background image to be displayed behind every page. See example with - [Infisical](https://infisical.com/docs) and [FRPC](https://frpc.io). - diff --git a/guides/authentication.mdx b/guides/authentication.mdx new file mode 100644 index 0000000..52c4e61 --- /dev/null +++ b/guides/authentication.mdx @@ -0,0 +1,7 @@ +--- +title: "Authentication" +description: "Obtain and use Auth0 JWT tokens to authenticate requests to the Trace FX API." +sidebarTitle: "Authentication" +--- + +{/* TODO: Write auth guide — Auth0 flow, obtaining tokens, X-Customer-Id claim, token rotation */} diff --git a/guides/environments.mdx b/guides/environments.mdx new file mode 100644 index 0000000..65c4433 --- /dev/null +++ b/guides/environments.mdx @@ -0,0 +1,6 @@ +--- +title: "Environments" +description: "Sandbox and production base URLs, and what differs between them." +--- + +{/* TODO: Write environments guide — sandbox vs production URLs, what resets, rate limits */} diff --git a/guides/principles/errors.mdx b/guides/principles/errors.mdx new file mode 100644 index 0000000..264067f --- /dev/null +++ b/guides/principles/errors.mdx @@ -0,0 +1,6 @@ +--- +title: "Errors" +description: "How errors are structured and how to handle them." +--- + +{/* TODO: Write errors guide — ErrorResponse shape (code, message, details), HTTP status codes, retry guidance */} diff --git a/guides/principles/idempotency.mdx b/guides/principles/idempotency.mdx new file mode 100644 index 0000000..d603df3 --- /dev/null +++ b/guides/principles/idempotency.mdx @@ -0,0 +1,6 @@ +--- +title: "Idempotency" +description: "Use the X-Idempotency-Key header to safely retry requests." +--- + +{/* TODO: Write idempotency guide — X-Idempotency-Key header, when required, conflict behavior (409) */} diff --git a/guides/principles/money.mdx b/guides/principles/money.mdx new file mode 100644 index 0000000..ca92745 --- /dev/null +++ b/guides/principles/money.mdx @@ -0,0 +1,6 @@ +--- +title: "Money and currencies" +description: "How monetary amounts are represented in the API." +--- + +{/* TODO: Write money guide — value in minor units (integer), asset as ISO 4217 currency code, supported currencies */} diff --git a/guides/principles/pagination.mdx b/guides/principles/pagination.mdx new file mode 100644 index 0000000..f75fec8 --- /dev/null +++ b/guides/principles/pagination.mdx @@ -0,0 +1,6 @@ +--- +title: "Pagination" +description: "Navigate large result sets with cursor-based pagination." +--- + +{/* TODO: Write pagination guide — cursor-based, meta object (previousCursor, nextCursor, total, totalMatches) */} diff --git a/guides/principles/versioning.mdx b/guides/principles/versioning.mdx new file mode 100644 index 0000000..8f236cd --- /dev/null +++ b/guides/principles/versioning.mdx @@ -0,0 +1,6 @@ +--- +title: "Versioning" +description: "How API versioning works via the X-Trace-Version header." +--- + +{/* TODO: Write versioning guide — X-Trace-Version header, default version behavior, deprecation policy */} diff --git a/index.mdx b/index.mdx index 15c23fb..6672c29 100644 --- a/index.mdx +++ b/index.mdx @@ -1,97 +1,6 @@ --- -title: "Introduction" -description: "Welcome to the new home for your documentation" +title: "Trace Finance Developer Docs" +description: "Build multi-currency account and payment experiences with the Trace FX API." --- -## Setting up - -Get your documentation site up and running in minutes. - - - Follow our three step quickstart guide. - - -## Make it yours - -Design a docs site that looks great and empowers your users. - - - - Edit your docs locally and preview them in real time. - - - Customize the design and colors of your site to match your brand. - - - Organize your docs to help users find what they need and succeed with your product. - - - Auto-generate API documentation from OpenAPI specifications. - - - -## Create beautiful pages - -Everything you need to create world-class documentation. - - - - Use MDX to style your docs pages. - - - Add sample code to demonstrate how to use your product. - - - Display images and other media. - - - Write once and reuse across your docs. - - - -## Need inspiration? - - - Browse our showcase of exceptional documentation sites. - +{/* TODO: Write landing page content — what Trace FX is, who it's for, quick navigation cards */} diff --git a/journeys/deposit.mdx b/journeys/deposit.mdx new file mode 100644 index 0000000..5a6116e --- /dev/null +++ b/journeys/deposit.mdx @@ -0,0 +1,6 @@ +--- +title: "Make a deposit" +description: "How to create a deposit and track it through to completion." +--- + +{/* TODO: Write journey — intent-first and money-first deposit flows, webhook events, status transitions */} diff --git a/journeys/open-brl-account.mdx b/journeys/open-brl-account.mdx new file mode 100644 index 0000000..b79b49c --- /dev/null +++ b/journeys/open-brl-account.mdx @@ -0,0 +1,6 @@ +--- +title: "Open a BRL account" +description: "Step-by-step guide to opening a Brazilian Real account for an account owner." +--- + +{/* TODO: Write journey — end-to-end flow from create-account to active account with BRL funding instructions */} diff --git a/journeys/open-crypto-account.mdx b/journeys/open-crypto-account.mdx new file mode 100644 index 0000000..4fb5e98 --- /dev/null +++ b/journeys/open-crypto-account.mdx @@ -0,0 +1,6 @@ +--- +title: "Open a crypto account" +description: "Step-by-step guide to opening a crypto asset account for an account owner." +--- + +{/* TODO: Write journey — end-to-end flow from create-account to active account with crypto wallet */} diff --git a/journeys/swap.mdx b/journeys/swap.mdx new file mode 100644 index 0000000..e83c649 --- /dev/null +++ b/journeys/swap.mdx @@ -0,0 +1,6 @@ +--- +title: "Execute a swap" +description: "How to convert between currencies using the swap endpoint." +--- + +{/* TODO: Write journey — swap flow, quote integration, forward-only execution */} diff --git a/journeys/withdrawal.mdx b/journeys/withdrawal.mdx new file mode 100644 index 0000000..3aacb66 --- /dev/null +++ b/journeys/withdrawal.mdx @@ -0,0 +1,6 @@ +--- +title: "Make a withdrawal" +description: "How to create a withdrawal and handle the review process." +--- + +{/* TODO: Write journey — withdrawal flow with and without compliance review, beneficiary setup */} diff --git a/quickstart.mdx b/quickstart.mdx index c711458..587714c 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -1,80 +1,6 @@ --- title: "Quickstart" -description: "Start building awesome documentation in minutes" +description: "Make your first API request to Trace FX in under five minutes." --- -## Get started in three steps - -Get your documentation site running locally and make your first customization. - -### Step 1: Set up your local environment - - - - During the onboarding process, you created a GitHub repository with your docs content if you didn't already have one. You can find a link to this repository in your [dashboard](https://dashboard.mintlify.com). - - To clone the repository locally so that you can make and preview changes to your docs, follow the [Cloning a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) guide in the GitHub docs. - - - 1. Install the Mintlify CLI: `npm i -g mint` - 2. Navigate to your docs directory and run: `mint dev` - 3. Open `http://localhost:3000` to see your docs live! - - Your preview updates automatically as you edit files. - - - -### Step 2: Deploy your changes - - - - Install the Mintlify GitHub app from your [dashboard](https://dashboard.mintlify.com/settings/organization/github-app). - - Our GitHub app automatically deploys your changes to your docs site, so you don't need to manage deployments yourself. - - - For a first change, let's update the name and colors of your docs site. - - 1. Open `docs.json` in your editor. - 2. Change the `"name"` field to your project name. - 3. Update the `"colors"` to match your brand. - 4. Save and see your changes instantly at `http://localhost:3000`. - - Try changing the primary color to see an immediate difference! - - - -### Step 3: Go live - - - 1. Commit and push your changes. - 2. Your docs will update and be live in moments! - - -## Next steps - -Now that you have your docs running, explore these key features: - - - - - Learn MDX syntax and start writing your documentation. - - - - Make your docs match your brand perfectly. - - - - Include syntax-highlighted code blocks. - - - - Auto-generate API docs from OpenAPI specs. - - - - - - **Need help?** See our [full documentation](https://mintlify.com/docs) or join our [community](https://mintlify.com/community). - +{/* TODO: Write quickstart — get credentials, hit sandbox, see a response */} diff --git a/snippets/auth-header.mdx b/snippets/auth-header.mdx new file mode 100644 index 0000000..4e600ac --- /dev/null +++ b/snippets/auth-header.mdx @@ -0,0 +1,7 @@ +All requests require a valid Auth0 JWT in the `Authorization` header: + +```bash +Authorization: Bearer +``` + +See [Authentication](/guides/authentication) for how to obtain a token. diff --git a/snippets/error-response.mdx b/snippets/error-response.mdx new file mode 100644 index 0000000..2e4d482 --- /dev/null +++ b/snippets/error-response.mdx @@ -0,0 +1,11 @@ +All errors follow the same shape: + +```json +{ + "code": "ERROR_CODE", + "message": "Human-readable description of what went wrong", + "details": {} +} +``` + +See [Errors](/guides/principles/errors) for error handling details. diff --git a/snippets/idempotency-note.mdx b/snippets/idempotency-note.mdx new file mode 100644 index 0000000..950bba4 --- /dev/null +++ b/snippets/idempotency-note.mdx @@ -0,0 +1,5 @@ + + This endpoint requires the `X-Idempotency-Key` header on every request. + Use a unique key (such as a UUID) per operation to safely retry on network failures. + See [Idempotency](/guides/principles/idempotency) for details. + diff --git a/snippets/money-format.mdx b/snippets/money-format.mdx new file mode 100644 index 0000000..c46576a --- /dev/null +++ b/snippets/money-format.mdx @@ -0,0 +1,10 @@ +Monetary amounts use **minor units** (integers) with an ISO 4217 currency code: + +```json +{ + "value": 500000, + "asset": "BRL" +} +``` + +The example above represents **R$ 5.000,00** (5,000 BRL). See [Money and currencies](/guides/principles/money). diff --git a/snippets/pagination-response.mdx b/snippets/pagination-response.mdx new file mode 100644 index 0000000..15ce66f --- /dev/null +++ b/snippets/pagination-response.mdx @@ -0,0 +1,15 @@ +List endpoints return a paginated response: + +```json +{ + "data": [], + "meta": { + "previousCursor": "string or null", + "nextCursor": "string or null", + "total": 42, + "totalMatches": 42 + } +} +``` + +Pass `nextCursor` as a query parameter to fetch the next page. See [Pagination](/guides/principles/pagination). diff --git a/snippets/sandbox-note.mdx b/snippets/sandbox-note.mdx new file mode 100644 index 0000000..bb193e9 --- /dev/null +++ b/snippets/sandbox-note.mdx @@ -0,0 +1,5 @@ + + This guide uses the **sandbox** environment. Replace `faas.sandbox.tracefinance.io` + with `faas.tracefinance.io` when you move to production. + See [Environments](/guides/environments) for details. + diff --git a/snippets/snippet-intro.mdx b/snippets/snippet-intro.mdx deleted file mode 100644 index e20fbb6..0000000 --- a/snippets/snippet-intro.mdx +++ /dev/null @@ -1,4 +0,0 @@ -One of the core principles of software development is DRY (Don't Repeat -Yourself). This is a principle that applies to documentation as -well. If you find yourself repeating the same content in multiple places, you -should consider creating a custom snippet to keep your content in sync. diff --git a/snippets/version-header.mdx b/snippets/version-header.mdx new file mode 100644 index 0000000..0781ab3 --- /dev/null +++ b/snippets/version-header.mdx @@ -0,0 +1,5 @@ + + Include the `X-Trace-Version` header to pin your integration to a specific + API version. If omitted, the default version is used. + See [Versioning](/guides/principles/versioning). + diff --git a/webhooks/events.mdx b/webhooks/events.mdx new file mode 100644 index 0000000..4e91367 --- /dev/null +++ b/webhooks/events.mdx @@ -0,0 +1,11 @@ +--- +title: "Event reference" +description: "Complete catalog of webhook events grouped by domain." +sidebarTitle: "Event reference" +--- + +{/* TODO: Write event reference — grouped by domain: + Account events: account.created, account.activated, account.deactivated, etc. + Payment events: operation.created, operation.completed, operation.failed, etc. + Beneficiary events: beneficiary.created, beneficiary.approved, beneficiary.rejected +*/} diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx new file mode 100644 index 0000000..c9789f1 --- /dev/null +++ b/webhooks/overview.mdx @@ -0,0 +1,7 @@ +--- +title: "Webhooks overview" +description: "Receive real-time notifications when events occur on the Trace FX platform." +sidebarTitle: "Overview" +--- + +{/* TODO: Write webhook overview — setup, endpoint requirements, signatures, retry policy, delivery guarantees */}