Skip to content

API Workflows

Eric Fitzgerald edited this page Jan 24, 2026 · 2 revisions

API Workflows

This page provides common API workflow patterns with complete code examples in multiple programming languages.

Workflow Specifications

Complete Workflow Definitions:

  • File: api-workflows.json
  • Version: 1.0.0
  • Description: Comprehensive API workflow patterns for TMI

Authentication Workflow

OAuth 2.0 Authorization Code Flow

Complete authentication workflow to obtain JWT token.

Flow:

1. GET /oauth2/providers → List available providers
2. GET /oauth2/authorize → Initiate OAuth flow
3. User authenticates with provider
4. GET /oauth2/callback → Receive authorization code
5. POST /oauth2/token → Exchange code for JWT token
6. GET /oauth2/userinfo → Verify authentication

Python Example:

import requests
from urllib.parse import urlencode

# Configuration
BASE_URL = 'https://api.tmi.dev'
CLIENT_ID = 'your-client-id'
REDIRECT_URI = 'https://your-app.com/callback'

def authenticate():
    """Complete OAuth authentication flow"""

    # 1. List providers
    providers = requests.get(f'{BASE_URL}/oauth2/providers').json()
    print('Available providers:', providers)

    # 2. Build authorization URL
    auth_params = {
        'response_type': 'code',
        'client_id': CLIENT_ID,
        'redirect_uri': REDIRECT_URI,
        'scope': 'openid email profile',
        'state': 'random-state-string'
    }
    auth_url = f'{BASE_URL}/oauth2/authorize?{urlencode(auth_params)}'

    print('Visit this URL to authenticate:')
    print(auth_url)

    # 3. User authenticates (browser flow)
    # Provider redirects to: {REDIRECT_URI}?code={auth_code}&state={state}

    # 4. Exchange authorization code for token
    auth_code = input('Enter authorization code: ')

    token_response = requests.post(
        f'{BASE_URL}/oauth2/token',
        data={
            'grant_type': 'authorization_code',
            'code': auth_code,
            'redirect_uri': REDIRECT_URI,
            'client_id': CLIENT_ID
        }
    )

    tokens = token_response.json()
    access_token = tokens['access_token']

    # 5. Verify token by getting user info
    user_info = requests.get(
        f'{BASE_URL}/oauth2/userinfo',
        headers={'Authorization': f'Bearer {access_token}'}
    ).json()

    print(f"Authenticated as: {user_info['email']}")

    return access_token

JavaScript Example:

// OAuth authentication using PKCE (for browser-based apps)
async function authenticate() {
    const BASE_URL = 'https://api.tmi.dev';
    const CLIENT_ID = 'your-client-id';
    const REDIRECT_URI = 'https://your-app.com/callback';

    // 1. Generate PKCE code verifier and challenge
    const codeVerifier = generateRandomString(128);
    const codeChallenge = await generateCodeChallenge(codeVerifier);

    // 2. Store code verifier for later
    sessionStorage.setItem('code_verifier', codeVerifier);

    // 3. Build authorization URL
    const authParams = new URLSearchParams({
        response_type: 'code',
        client_id: CLIENT_ID,
        redirect_uri: REDIRECT_URI,
        scope: 'openid email profile',
        state: generateRandomString(32),
        code_challenge: codeChallenge,
        code_challenge_method: 'S256'
    });

    // 4. Redirect to authorization endpoint
    window.location.href = `${BASE_URL}/oauth2/authorize?${authParams}`;
}

// Handle OAuth callback
async function handleCallback() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const codeVerifier = sessionStorage.getItem('code_verifier');

    // Exchange code for token
    const response = await fetch(`${BASE_URL}/oauth2/token`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: REDIRECT_URI,
            client_id: CLIENT_ID,
            code_verifier: codeVerifier
        })
    });

    const tokens = await response.json();
    localStorage.setItem('access_token', tokens.access_token);

    // Get user info
    const userInfo = await fetch(`${BASE_URL}/oauth2/userinfo`, {
        headers: { 'Authorization': `Bearer ${tokens.access_token}` }
    }).then(r => r.json());

    console.log('Authenticated as:', userInfo.email);
}

// Helper functions
function generateRandomString(length) {
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let text = '';
    for (let i = 0; i < length; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
}

async function generateCodeChallenge(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const hash = await crypto.subtle.digest('SHA-256', data);
    return base64URLEncode(hash);
}

function base64URLEncode(buffer) {
    const bytes = new Uint8Array(buffer);
    let str = '';
    for (const byte of bytes) {
        str += String.fromCharCode(byte);
    }
    return btoa(str)
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

Threat Model CRUD Workflow

Complete Threat Model Lifecycle

Create, read, update, and delete threat models.

Python Example:

import requests

BASE_URL = 'https://api.tmi.dev'
TOKEN = 'your-jwt-token'

headers = {
    'Authorization': f'Bearer {TOKEN}',
    'Content-Type': 'application/json'
}

def create_threat_model():
    """Create a new threat model"""
    response = requests.post(
        f'{BASE_URL}/threat_models',
        headers=headers,
        json={
            'name': 'Production API Security',
            'description': 'Comprehensive threat model for production APIs',
            'authorization': [
                {
                    'subject': 'security-team',
                    'subject_type': 'group',
                    'role': 'writer'
                }
            ],
            'metadata': {
                'project': 'API-Gateway',
                'compliance': 'GDPR'
            }
        }
    )

    threat_model = response.json()
    print(f"Created threat model: {threat_model['id']}")
    return threat_model

def list_threat_models():
    """List all accessible threat models"""
    response = requests.get(
        f'{BASE_URL}/threat_models',
        headers=headers,
        params={
            'limit': 50,
            'sort': '-created_at'
        }
    )

    data = response.json()
    print(f"Found {data['total']} threat models")
    return data['items']

def get_threat_model(threat_model_id):
    """Get specific threat model with details"""
    response = requests.get(
        f'{BASE_URL}/threat_models/{threat_model_id}',
        headers=headers
    )

    threat_model = response.json()
    print(f"Threat Model: {threat_model['name']}")
    print(f"  Diagrams: {threat_model.get('diagram_count', 0)}")
    print(f"  Threats: {threat_model.get('threat_count', 0)}")
    return threat_model

def update_threat_model(threat_model_id):
    """Update threat model (partial)"""
    response = requests.patch(
        f'{BASE_URL}/threat_models/{threat_model_id}',
        headers=headers,
        json={
            'description': 'Updated description with new scope',
            'metadata': {
                'project': 'API-Gateway',
                'compliance': 'GDPR,HIPAA'
            }
        }
    )

    updated = response.json()
    print(f"Updated threat model: {updated['id']}")
    return updated

def delete_threat_model(threat_model_id):
    """Delete threat model"""
    response = requests.delete(
        f'{BASE_URL}/threat_models/{threat_model_id}',
        headers=headers
    )

    if response.status_code == 204:
        print(f"Deleted threat model: {threat_model_id}")
    return response.status_code == 204

# Complete workflow
if __name__ == '__main__':
    # Create
    tm = create_threat_model()

    # List
    all_models = list_threat_models()

    # Get
    details = get_threat_model(tm['id'])

    # Update
    updated = update_threat_model(tm['id'])

    # Delete
    deleted = delete_threat_model(tm['id'])

Go Example:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

const (
    BaseURL = "https://api.tmi.dev"
    Token   = "your-jwt-token"
)

type ThreatModel struct {
    ID            string                 `json:"id,omitempty"`
    Name          string                 `json:"name"`
    Description   string                 `json:"description"`
    Authorization []Authorization        `json:"authorization,omitempty"`
    Metadata      map[string]interface{} `json:"metadata,omitempty"`
}

type Authorization struct {
    Subject     string `json:"subject"`
    SubjectType string `json:"subject_type"`
    Role        string `json:"role"`
}

func createThreatModel() (*ThreatModel, error) {
    tm := &ThreatModel{
        Name:        "Production API Security",
        Description: "Comprehensive threat model for production APIs",
        Authorization: []Authorization{
            {
                Subject:     "security-team",
                SubjectType: "group",
                Role:        "writer",
            },
        },
        Metadata: map[string]interface{}{
            "project":    "API-Gateway",
            "compliance": "GDPR",
        },
    }

    body, _ := json.Marshal(tm)
    req, _ := http.NewRequest("POST", BaseURL+"/threat_models", bytes.NewBuffer(body))
    req.Header.Set("Authorization", "Bearer "+Token)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var created ThreatModel
    json.NewDecoder(resp.Body).Decode(&created)

    fmt.Printf("Created threat model: %s\n", created.ID)
    return &created, nil
}

func listThreatModels() ([]ThreatModel, error) {
    req, _ := http.NewRequest("GET", BaseURL+"/threat_models?limit=50", nil)
    req.Header.Set("Authorization", "Bearer "+Token)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var data struct {
        Items []ThreatModel `json:"items"`
        Total int           `json:"total"`
    }
    json.NewDecoder(resp.Body).Decode(&data)

    fmt.Printf("Found %d threat models\n", data.Total)
    return data.Items, nil
}

func main() {
    // Create
    tm, _ := createThreatModel()

    // List
    models, _ := listThreatModels()
    fmt.Printf("Total models: %d\n", len(models))

    // Get, update, delete would follow similar patterns
}

Diagram Creation and Editing

Create Diagram with Cells

Python Example:

def create_diagram(threat_model_id):
    """Create a data flow diagram"""
    response = requests.post(
        f'{BASE_URL}/threat_models/{threat_model_id}/diagrams',
        headers=headers,
        json={
            'title': 'System Architecture',
            'diagram_type': 'data_flow',
            'diagram_json': {
                'cells': [
                    {
                        'id': 'process-1',
                        'type': 'process',
                        'position': {'x': 100, 'y': 100},
                        'size': {'width': 120, 'height': 60},
                        'attrs': {
                            'label': {'text': 'API Gateway'}
                        }
                    },
                    {
                        'id': 'datastore-1',
                        'type': 'datastore',
                        'position': {'x': 300, 'y': 100},
                        'size': {'width': 100, 'height': 60},
                        'attrs': {
                            'label': {'text': 'Database'}
                        }
                    },
                    {
                        'id': 'link-1',
                        'type': 'link',
                        'source': {'id': 'process-1'},
                        'target': {'id': 'datastore-1'},
                        'attrs': {
                            'label': {'text': 'SQL Query'}
                        }
                    }
                ],
                'assets': [
                    {
                        'cell_id': 'datastore-1',
                        'name': 'User Database',
                        'sensitivity': 'high',
                        'description': 'Contains PII'
                    }
                ]
            }
        }
    )

    diagram = response.json()
    print(f"Created diagram: {diagram['id']}")
    return diagram

Threat Identification Workflow

Identify and Document Threats

Python Example:

def identify_threats(threat_model_id):
    """Create multiple threats for a threat model"""

    threats = [
        {
            'threat_model_id': threat_model_id,
            'title': 'SQL Injection Vulnerability',
            'description': 'User input not sanitized before SQL queries',
            'category': 'injection',
            'severity': 'high',
            'likelihood': 'medium',
            'impact': 'Data breach, unauthorized access',
            'mitigation': 'Use parameterized queries and input validation',
            'status': 'open',
            'affected_assets': ['User Database']
        },
        {
            'threat_model_id': threat_model_id,
            'title': 'Insufficient Logging',
            'description': 'Security events not logged adequately',
            'category': 'logging',
            'severity': 'medium',
            'likelihood': 'high',
            'impact': 'Delayed incident detection',
            'mitigation': 'Implement comprehensive logging',
            'status': 'open'
        },
        {
            'threat_model_id': threat_model_id,
            'title': 'Missing Authentication',
            'description': 'API endpoint lacks authentication',
            'category': 'authentication',
            'severity': 'critical',
            'likelihood': 'high',
            'impact': 'Unauthorized access to sensitive data',
            'mitigation': 'Add OAuth 2.0 authentication',
            'status': 'open'
        }
    ]

    created_threats = []
    for threat_data in threats:
        response = requests.post(
            f'{BASE_URL}/threats',
            headers=headers,
            json=threat_data
        )
        threat = response.json()
        created_threats.append(threat)
        print(f"Created threat: {threat['title']}")

    return created_threats

def update_threat_status(threat_id, status, resolution_notes):
    """Update threat status to mitigated/resolved"""
    response = requests.patch(
        f'{BASE_URL}/threats/{threat_id}',
        headers=headers,
        json={
            'status': status,
            'resolution_notes': resolution_notes,
            'resolved_at': '2025-01-15T10:00:00Z'
        }
    )

    updated = response.json()
    print(f"Updated threat status: {updated['status']}")
    return updated

Webhook Integration Workflow

Set Up Webhook Notifications

Python Example:

def setup_webhook_integration():
    """Complete webhook setup workflow"""

    # 1. Create webhook subscription
    response = requests.post(
        f'{BASE_URL}/webhook/subscriptions',
        headers=headers,
        json={
            'name': 'Slack Notifications',
            'url': 'https://your-service.com/webhooks/tmi',
            'events': [
                'threat_model.created',
                'threat_model.updated',
                'threat.created'
            ],
            'secret': 'your-webhook-secret',
            'threat_model_id': None  # All threat models
        }
    )

    subscription = response.json()
    print(f"Created webhook: {subscription['id']}")
    print(f"Status: {subscription['status']}")  # pending_verification

    # 2. Verify endpoint responds to challenge
    # (Your webhook endpoint must respond to challenge request)

    # 3. Check subscription status
    response = requests.get(
        f"{BASE_URL}/webhook/subscriptions/{subscription['id']}",
        headers=headers
    )

    current = response.json()
    print(f"Current status: {current['status']}")  # active after verification

    # 4. Monitor deliveries
    response = requests.get(
        f'{BASE_URL}/webhook/deliveries',
        headers=headers,
        params={'subscription_id': subscription['id']}
    )

    deliveries = response.json()
    print(f"Delivery count: {len(deliveries['items'])}")

    for delivery in deliveries['items']:
        print(f"  {delivery['event_type']}: {delivery['status']}")

    return subscription

Batch Operations Workflow

Process Multiple Resources

Python Example:

def batch_create_resources(threat_model_id):
    """Create multiple resources efficiently"""

    # Create multiple threats in parallel
    import concurrent.futures

    threat_templates = [
        {
            'title': f'Threat {i}',
            'description': f'Description for threat {i}',
            'category': 'other',
            'severity': 'medium',
            'threat_model_id': threat_model_id
        }
        for i in range(10)
    ]

    def create_single_threat(threat_data):
        response = requests.post(
            f'{BASE_URL}/threats',
            headers=headers,
            json=threat_data
        )
        return response.json()

    # Create threats in parallel
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        threats = list(executor.map(create_single_threat, threat_templates))

    print(f"Created {len(threats)} threats")
    return threats

Export and Reporting Workflow

Generate Threat Model Report

Python Example:

def generate_threat_report(threat_model_id):
    """Generate comprehensive threat model report"""

    # 1. Get threat model
    tm = requests.get(
        f'{BASE_URL}/threat_models/{threat_model_id}',
        headers=headers
    ).json()

    # 2. Get all threats
    threats = requests.get(
        f'{BASE_URL}/threats',
        headers=headers,
        params={'threat_model_id': threat_model_id, 'limit': 500}
    ).json()['items']

    # 3. Get all diagrams
    diagrams = requests.get(
        f'{BASE_URL}/threat_models/{threat_model_id}/diagrams',
        headers=headers
    ).json()['items']

    # 4. Build report
    report = {
        'threat_model': {
            'name': tm['name'],
            'description': tm['description'],
            'owner': tm['owner'],
            'created_at': tm['created_at']
        },
        'statistics': {
            'total_threats': len(threats),
            'critical_threats': len([t for t in threats if t['severity'] == 'critical']),
            'high_threats': len([t for t in threats if t['severity'] == 'high']),
            'open_threats': len([t for t in threats if t['status'] == 'open']),
            'mitigated_threats': len([t for t in threats if t['status'] == 'mitigated'])
        },
        'threats_by_category': {},
        'diagrams': len(diagrams),
        'threats': threats
    }

    # Group threats by category
    for threat in threats:
        category = threat.get('category', 'other')
        if category not in report['threats_by_category']:
            report['threats_by_category'][category] = []
        report['threats_by_category'][category].append(threat)

    # 5. Export report
    import json
    with open(f'threat_report_{threat_model_id}.json', 'w') as f:
        json.dump(report, f, indent=2)

    print(f"Generated report: threat_report_{threat_model_id}.json")
    return report

Error Handling Pattern

Robust API Client

Python Example:

import time
from requests.exceptions import RequestException

def api_call_with_retry(method, url, **kwargs):
    """Make API call with exponential backoff retry"""

    max_retries = 3
    base_delay = 1

    for attempt in range(max_retries):
        try:
            response = requests.request(method, url, **kwargs)

            # Handle different status codes
            if response.status_code == 200 or response.status_code == 201:
                return response.json()

            elif response.status_code == 204:
                return None  # Success, no content

            elif response.status_code == 401:
                # Token expired, re-authenticate
                raise Exception('Authentication required')

            elif response.status_code == 429:
                # Rate limited, wait and retry
                retry_after = int(response.headers.get('Retry-After', base_delay * (2 ** attempt)))
                print(f'Rate limited, waiting {retry_after}s')
                time.sleep(retry_after)
                continue

            elif response.status_code >= 500:
                # Server error, retry
                if attempt < max_retries - 1:
                    delay = base_delay * (2 ** attempt)
                    print(f'Server error, retrying in {delay}s')
                    time.sleep(delay)
                    continue

            # Other errors
            error = response.json()
            raise Exception(f"API Error: {error.get('message', 'Unknown error')}")

        except RequestException as e:
            if attempt < max_retries - 1:
                delay = base_delay * (2 ** attempt)
                print(f'Network error, retrying in {delay}s: {e}')
                time.sleep(delay)
            else:
                raise

    raise Exception('Max retries exceeded')

Related Documentation

Generating Workflow Documentation

For developers needing to update or extend the API workflow documentation, use this AI-assisted prompt template. This is useful when the OpenAPI specification has been updated with new endpoints or when additional test workflows need to be documented.

Prompt Template for Workflow Generation

When working with an AI assistant (Claude, GPT-4, etc.), provide the following context and instructions:

You are an experienced software developer specializing in implementing back-end API servers, and you are passionate about reliability, which you have learned from experience requires thorough testing.

You have been assigned the task of analyzing the TMI OpenAPI 3.0 specification JSON file docs/reference/apis/tmi-openapi.json and generating a json file that will be used to drive automated testing.

The openapi spec defines hierarchical objects, such as "diagram" objects as children of "threat_model" objects. Most APIs require OAuth authentication/authorization (some are public and don't require authentication or authorization). We support the "authorization code with PKCE" OAuth flow.

The problem with automated testing of complex APIs is that many API calls depend on state established with earlier API calls. For example, you must authenticate before you can create objects. You must create an object before you can create child objects of the original object. You can't delete an object that has not yet been created, and so forth.

The TMI API has many endpoints and is moderately complex. In order to test it thoroughly, you are going to need to build and/or use API test automation. And your task is to generate the documentation needed to build that automation. The OpenAPI spec defines the endpoints and schemas, but doesn't explicitly describe the order of operations for API calls that have other API calls as prerequisites, and that's what you're going to create.

Please create a structured JSON "workflows" file that outlines the required API call sequences for testing every supported HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) on every object and every property.

For each object (e.g., "threat_model", "diagram"):

- List dependencies: Prerequisite objects/actions (e.g., to operate on diagram, one must first create a threat model, since diagrams are child objects of threat model objects).

For each method:

- List the sequence, in order of prerequisite calls, starting from OAuth flow
- Example: To modify the name property of a diagram object, one must perform: OAuth authentication -> PUT /threat_model -> PUT /threat_model/{threat_model_id}/diagram -> PATCH /threat_model/{threat_model_id}/diagram/{diagram_id}/name.

Cover all properties: For PATCH/PUT, include sequences per property if dependencies differ.

Handle hierarchies: Ensure child operations require parent creation.

Output format: JSON with top-level keys as object names, sub-keys as methods, values as arrays of ordered steps (each step: { "action": "METHOD /path", "description": "brief purpose", "prereqs": ["prev-step-ids"] }).

You can see an example output file at docs/reference/apis/api-workflows.json

Ensure completeness for all paths, parameters, and auth in the spec.

Write the json file to the project root directory.

Key Concepts for Workflow Generation

  1. Hierarchical Dependencies: Diagrams are children of threat models, threats are children of threat models, etc.
  2. Authentication First: All protected endpoints require OAuth authentication via PKCE flow
  3. State Prerequisites: Create operations must precede update/delete operations
  4. Path Parameters: Child endpoints require parent IDs (e.g., /threat_models/{threat_model_id}/diagrams)

Output Format

The generated api-workflows.json should follow this structure:

{
  "version": "1.0.0",
  "workflows": {
    "object_name": {
      "method_name": [
        {
          "action": "METHOD /path",
          "description": "Brief purpose",
          "prereqs": ["prerequisite-step-id"]
        }
      ]
    }
  }
}

Related Files

  • OpenAPI Specification: docs/reference/apis/tmi-openapi.json
  • Existing Workflows: docs/reference/apis/api-workflows.json
  • Arazzo Specification: docs/reference/apis/tmi.arazzo.yaml (workflow sequences in OpenAPI Initiative format)

Clone this wiki locally