# Mining Structured API Documentation from SPA Doc Sites When researching third-party APIs, their docs are often served as single-page applications (apidoc-generated, Slate, ReadMe.io, SwaggerUI). The visible browser page is JS-rendered, but the *raw data payload* is typically available as static JSON/JS files. This reference documents techniques to find and extract them. ## Common SPA Doc Frameworks & Their Data Files ### 1. apidocjs (http://apidocjs.com) **Framework**: Uses Handlebars templates + a JSON data file. The page is an SPA that loads all endpoint data into memory. **Data files to look for**: | File | Contents | |------|----------| | `api_data.json` | Array of endpoint objects with type, url, title, parameters, responses, permissions | | `api_data.js` | Same data wrapped in `define({ "api": [...] })` (RequireJS/AMD) | | `api_project.json` | Project metadata: name, version, URL, group ordering | | `api_project.js` | Project metadata in AMD wrapper | | `api_doc_collection.json` | Postman v2 collection with bodies, headers, URLs, test scripts | **Extraction technique**: ```bash # Try the JSON version first (cleaner) curl -sL "https://apidocs.example.com/api_data.json" | python3 -m json.tool # If 404, try the JS version (has trailing commas — needs cleanup) curl -sL "https://apidocs.example.com/api_data.js" -o /tmp/data.js # Strip the AMD wrapper and fix trailing commas python3 -c " import re, json with open('/tmp/data.js') as f: content = f.read() # Extract JSON array from define({ 'api': [...] }) match = re.search(r'define\(\{ \"api\": (\[.*?\])\s*\}\);?\s*$', content, re.DOTALL) if match: raw = match.group(1) else: # Fallback: find outermost array start = content.index('[{') end = content.rindex('}]') + 2 raw = content[start:end] # Fix trailing commas (json doesn't allow them) raw = re.sub(r',\s*}', '}', raw) raw = re.sub(r',\s*]', ']', raw) data = json.loads(raw) print(json.dumps(data, indent=2)[:5000]) " ``` **What you get**: Full endpoint enumeration with: - HTTP method, URL template - Required/optional parameters with types - Response field schemas - Permission/scoping levels - Success/error response patterns - Example request bodies ### 2. Postman Collections Many doc sites offer a Postman v2 collection JSON as a downloadable test suite. **Finding it**: Check for: - `api_doc_collection.json` - `postman_collection.json` - A "Download Postman Collection" link in the docs **Extraction**: ```python import json with open('/tmp/postman.json') as f: data = json.load(f) def find_all_items(items): results = [] for item in items: name = item.get('name', '') if 'item' in item: results.extend(find_all_items(item['item'])) else: # Leaf node = actual API endpoint results.append((name, item)) return results items = find_all_items(data.get('item', [])) # Group endpoints by folder folders = {} for item in data.get('item', []): folder_name = item.get('name', '') if 'item' in item: for ep in item['item']: # Extract URL template req = ep.get('request', {}) url = req.get('url', {}) if isinstance(url, dict): url_template = url.get('raw', 'N/A') else: url_template = str(url) # Extract form params body = req.get('body', {}) params = [] if isinstance(body, dict): for mode in ['formdata', 'urlencoded']: for p in body.get(mode, []): params.append(p.get('key')) folders.setdefault(folder_name, []) folders[folder_name].append({ 'name': ep.get('name'), 'method': req.get('method', 'POST'), 'url': url_template, 'params': params }) ``` **What you get** from Postman collections that raw API JSON may not include: - Actual request body templates with placeholder values - Real URL patterns including base URL templates (`{{api_url}}`) - Authorization header templates - Response status codes and example bodies - Pre-request scripts and test assertions ### 3. Other Doc Frameworks — Quick Reference | Framework | Look for | |-----------|----------| | **Swagger/OpenAPI 2.0** | `swagger.json`, `api-docs`, `swagger.yaml` | | **OpenAPI 3.0** | `openapi.json`, `openapi.yaml`, `v3/api-docs` | | **Slate/Middleman** | No JSON payload — scrape rendered HTML or look for `source/includes/` | | **ReadMe.io** | Fetch with `?format=json` param, or look for `__NEXT_DATA__` in page HTML | | **Redoc/RapiDoc** | Usually loads an external OpenAPI spec URL — find the `` in HTML | | **Stoplight/Elements** | `` in page source | ## Parsing the Project Metadata The `api_project.json` or `api_project.js` tells you the API's: - Base URL (`url` field) - API version - Ordered endpoint groups (`order` array) - Auth mechanism description (in `header.content`) ```python # Sample extraction with open('/tmp/api_project.json') as f: project = json.load(f) base_url = project.get('url', 'N/A') api_name = project.get('name', 'N/A') version = project.get('version', 'N/A') groups_in_order = project.get('order', []) ``` ## Grouping and Aggregation Once you have the full endpoint list, aggregate by group to produce a high-level inventory: ``` === Agent (6 endpoints) POST ?object=agent&action=count | Count Agents | Scope: Reseller POST ?object=agent&action=create | Create an Agent | Scope: Reseller POST ?object=agent&action=delete | Delete Agents | Scope: Reseller POST ?object=agent&action=read | Read Agents | Scope: OMP POST ?object=agent&action=update | Update Agents | Scope: Reseller === Domain (6 endpoints) POST ?object=domain&action=count | Count Domains | Scope: OMP POST ?object=domain&action=create | Create Domain | Scope: OMP POST ?object=domain&action=read | Read Domain | Scope: OMP POST ?object=domain&action=read&billing=yes | Read Billing | Scope: OMP POST ?object=domain&action=update | Update Domain | Scope: OMP POST ?object=domain&action=delete | Delete Domain | Scope: OMP ``` ## Auth & Scope Mapping Most API doc sites define permission/scope levels per endpoint. Extract a scope matrix: ```python scopes = {} for entry in data: perm_names = [p['name'] for p in entry.get('permission', [])] group = entry.get('group', '') for perm in perm_names: scopes.setdefault(perm, set()).add(group) for scope, groups in sorted(scopes.items()): print(f"{scope}: {', '.join(sorted(groups))}") ``` This tells you which auth level can access which functional area — critical for integration planning. ## Pitfalls - **api_data.json with trailing commas**: Not valid JSON. The JS AMD wrapper version (`api_data.js`) often has the same problem. Always fix trailing commas before parsing. - **Huge files**: Some doc sites have 800K+ JSON files with 30K+ lines. Don't dump the whole thing at once — parse programmatically and extract the structured summary. - **URL templates**: URLs in the JSON often have template variables like `{{api_url}}` or use relative paths (`?object=x&action=y`). The base URL is in `api_project.json`. - **Missing groups**: Swagger/OpenAPI may have paths that don't correspond to clean groups — you'll need to aggregate by tag or path prefix. - **Authentication**: Doc site data files are public and contain endpoint schemas but NOT real credentials. Don't confuse the two. - **Version drift**: The Postman collection and api_data.json may be out of sync. Cross-reference when possible.