Files

7.7 KiB

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:

# 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:

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 <redoc spec-url="..."> in HTML
Stoplight/Elements <elements-api apiDescriptionUrl="..."> 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)
# 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:

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.