Lab 07 β Security API Integration
Mission Information
Section titled βMission InformationβDifficulty: Intermediate β Advanced
Estimated Time: 120β180 minutes
Primary Language: Python
Security Domain: Security Automation / SOC / Cloud Security / Vulnerability Management
Environment: Local controlled lab
Automation Type: Defensive API Integration
Mission
Section titled βMissionβYour task is to build a reusable Python security API client.
Instead of manually opening a security dashboard and exporting data, your script will interact with a controlled local training API.
You will learn how to:
AUTHENTICATE
SEND API REQUESTS
USE HTTP METHODS
PASS PARAMETERS
READ JSON
VALIDATE RESPONSES
HANDLE TIMEOUTS
HANDLE ERRORS
IMPLEMENT RETRIES
UNDERSTAND RATE LIMITS
NORMALIZE SECURITY DATA
GENERATE REPORTSThe final workflow will be:
SECURITY PLATFORM βREST API βPYTHON CLIENT βAUTHENTICATE βREQUEST βVALIDATE RESPONSE βPARSE JSON βNORMALIZE βANALYZE βREPORTWhy This Lab Matters
Section titled βWhy This Lab MattersβModern security platforms are heavily API-driven.
Examples include:
SIEM
EDR / XDR
CLOUD SECURITY PLATFORMS
VULNERABILITY MANAGEMENT
THREAT INTELLIGENCE
IDENTITY SECURITY
TICKETING SYSTEMS
SOAR
ASSET MANAGEMENT
COMPLIANCE PLATFORMSA security automation engineer may need to connect:
VULNERABILITY PLATFORM βPYTHON βTICKETING SYSTEMor:
SIEM ALERT βAPI βTHREAT INTELLIGENCE βENRICHED ALERTor:
CLOUD SECURITY API βFINDINGS βNORMALIZATION βSECURITY DASHBOARDLearning Objectives
Section titled βLearning ObjectivesβBy completing this lab, you should be able to:
UNDERSTAND REST APIs
UNDERSTAND HTTP METHODS
USE API ENDPOINTS
USE QUERY PARAMETERS
USE HEADERS
USE BEARER TOKENS
PROTECT API SECRETS
SEND GET REQUESTS
SEND CONTROLLED POST REQUESTS
PARSE JSON
VALIDATE HTTP STATUS CODES
CONFIGURE TIMEOUTS
HANDLE NETWORK ERRORS
IMPLEMENT RETRIES
UNDERSTAND BACKOFF
UNDERSTAND RATE LIMITING
BUILD PAGINATION LOGIC
NORMALIZE API DATA
EXPORT SECURITY REPORTS
BUILD A REUSABLE API CLIENTFinal Architecture
Section titled βFinal Architectureβ SECURITY AUTOMATION β βββββββββββββββ β API CLIENT β ββββββββ¬βββββββ β AUTHENTICATE β HTTP REQUEST β API ENDPOINT β HTTP RESPONSE β ββββββββββββΌβββββββββββ β β β STATUS HEADERS JSON β β β ββββββββββββΌβββββββββββ β VALIDATE β NORMALIZE β ANALYZE β SECURITY REPORTAuthorization and Safety
Section titled βAuthorization and SafetyβThis lab uses:
A LOCAL TRAINING APIand:
SYNTHETIC SECURITY DATADo not connect automation to a production security platform until you understand:
API PERMISSIONS
DATA SENSITIVITY
RATE LIMITS
CHANGE IMPACT
ERROR HANDLING
AUDIT REQUIREMENTSFor this lab, API actions remain:
READ-ONLYexcept for an optional local training POST request that creates only synthetic lab records.
01 β Create the Lab Workspace
Section titled β01 β Create the Lab WorkspaceβCreate:
security-api-integration/|+-- api/|+-- data/|+-- reports/|+-- src/|+-- tests/|+-- README.mdLinux/macOS:
mkdir -p security-api-integration/{api,data,reports,src,tests}cd security-api-integrationPowerShell:
mkdir security-api-integration
cd security-api-integration
mkdir apimkdir datamkdir reportsmkdir srcmkdir tests02 β Verify Python
Section titled β02 β Verify PythonβRun:
python --versionRecommended:
Python 3.10+03 β API Concepts
Section titled β03 β API ConceptsβBefore coding, understand:
CLIENTThe application making the request.
SERVERThe application responding.
ENDPOINTA specific API path.
Example:
/api/findingsREQUESTWhat the client sends.
RESPONSEWhat the server returns.
04 β REST API Mental Model
Section titled β04 β REST API Mental ModelβCLIENT βHTTP REQUEST βENDPOINT βSERVER βHTTP RESPONSE βCLIENT05 β Understand HTTP Methods
Section titled β05 β Understand HTTP MethodsβCommon methods:
| Method | Typical Purpose |
|---|---|
| GET | Retrieve data |
| POST | Create data |
| PUT | Replace data |
| PATCH | Modify part of a resource |
| DELETE | Remove data |
For this lab, focus mainly on:
GETand one controlled local:
POST06 β Understand HTTP Status Codes
Section titled β06 β Understand HTTP Status CodesβImportant examples:
200Success
201Created
400Bad Request
401Unauthorized
403Forbidden
404Not Found
429Too Many Requests
500Server Error
503Service Unavailable07 β Why Status Codes Matter
Section titled β07 β Why Status Codes MatterβDo not write:
REQUEST COMPLETED=SUCCESSThe actual workflow is:
REQUEST SENT βRESPONSE RECEIVED βSTATUS CODE? βVALID? βPARSE DATA08 β Build a Local Training API
Section titled β08 β Build a Local Training APIβTo keep the lab controlled, create a small local API.
Create:
api/training_api.py09 β Use Python Standard Library
Section titled β09 β Use Python Standard LibraryβFor the server, use:
from http.server import BaseHTTPRequestHandler, HTTPServerfrom urllib.parse import urlparse, parse_qsimport jsonThis avoids requiring an external framework for the lab.
10 β Create Synthetic Security Findings
Section titled β10 β Create Synthetic Security FindingsβAdd:
FINDINGS = [ { "id": "F-1001", "asset": "WEB01", "type": "vulnerability", "severity": "critical", "status": "open", "score": 9.8 }, { "id": "F-1002", "asset": "DB01", "type": "vulnerability", "severity": "high", "status": "open", "score": 8.2 }, { "id": "F-1003", "asset": "APP01", "type": "configuration", "severity": "medium", "status": "open", "score": 5.4 }, { "id": "F-1004", "asset": "VPN01", "type": "configuration", "severity": "high", "status": "closed", "score": 7.6 }, { "id": "F-1005", "asset": "JUMP01", "type": "identity", "severity": "critical", "status": "open", "score": 9.1 }]11 β Define a Training Token
Section titled β11 β Define a Training TokenβAdd:
API_TOKEN = "training-token-123"This is acceptable only because:
THE SERVER IS LOCAL
THE TOKEN IS SYNTHETIC
THE VALUE HAS NO REAL PRIVILEGEDo not copy this pattern for production credentials.
12 β Create the API Handler
Section titled β12 β Create the API HandlerβAdd:
class TrainingAPIHandler( BaseHTTPRequestHandler): def send_json( self, data, status=200 ): body = json.dumps( data ).encode( "utf-8" )
self.send_response( status )
self.send_header( "Content-Type", "application/json" )
self.send_header( "Content-Length", str( len(body) ) )
self.end_headers()
self.wfile.write( body )13 β Add Authentication Checking
Section titled β13 β Add Authentication CheckingβAdd:
def is_authorized( self ): authorization = ( self.headers.get( "Authorization", "" ) )
expected = ( f"Bearer {API_TOKEN}" )
return ( authorization == expected )14 β Why Use an Authorization Header?
Section titled β14 β Why Use an Authorization Header?βA common API pattern is:
Authorization:Bearer TOKENConceptually:
CLIENT βTOKEN βAPI βIDENTITY / PERMISSION CHECK15 β Create the GET Handler
Section titled β15 β Create the GET HandlerβAdd:
def do_GET( self ): if not self.is_authorized(): self.send_json( { "error": "unauthorized" }, 401 ) return
parsed = urlparse( self.path )
if parsed.path == "/api/findings": self.handle_findings( parsed ) return
if parsed.path == "/api/health": self.send_json( { "status": "ok" } ) return
self.send_json( { "error": "not_found" }, 404 )16 β Add Query Parameter Filtering
Section titled β16 β Add Query Parameter FilteringβCreate:
def handle_findings( self, parsed ): query = parse_qs( parsed.query )
severity = query.get( "severity", [None] )[0]
status = query.get( "status", [None] )[0]
results = FINDINGS
if severity: results = [ item for item in results if item[ "severity" ] == severity.lower() ]
if status: results = [ item for item in results if item[ "status" ] == status.lower() ]
self.send_json( { "count": len(results),
"results": results } )17 β Start the Training API
Section titled β17 β Start the Training APIβAdd:
def main(): server = HTTPServer( ( "127.0.0.1", 8080 ), TrainingAPIHandler )
print( "Training API listening on " "http://127.0.0.1:8080" )
server.serve_forever()Then:
if __name__ == "__main__": main()18 β Start the Server
Section titled β18 β Start the ServerβRun in Terminal 1:
python api/training_api.pyExpected:
Training API listening on http://127.0.0.1:808019 β Test the Health Endpoint
Section titled β19 β Test the Health EndpointβFor now use Python rather than relying on additional tools.
Create:
src/test_connection.pyAdd:
from urllib.request import Request, urlopen
request = Request( "http://127.0.0.1:8080/api/health")
request.add_header( "Authorization", "Bearer training-token-123")
with urlopen( request, timeout=5) as response:
print( response.read().decode( "utf-8" ) )Run in Terminal 2:
python src/test_connection.pyExpected:
{"status": "ok"}20 β Test Unauthorized Access
Section titled β20 β Test Unauthorized AccessβChange the token temporarily to:
wrong-tokenRun again.
Expected:
HTTP 401Restore the correct synthetic token afterward.
21 β Why Authentication Errors Matter
Section titled β21 β Why Authentication Errors MatterβYour script must distinguish between:
NETWORK FAILUREand:
AUTHENTICATION FAILUREbecause the troubleshooting action is different.
22 β Create the Production-Style Client
Section titled β22 β Create the Production-Style ClientβCreate:
src/security_api_client.pyStart with:
from pathlib import Pathfrom urllib.request import Request, urlopenfrom urllib.parse import urlencodefrom urllib.error import HTTPError, URLErrorimport csvimport jsonimport loggingimport osimport time23 β Define Project Paths
Section titled β23 β Define Project PathsβAdd:
BASE_DIR = Path(__file__).resolve().parent.parent
REPORT_DIR = BASE_DIR / "reports"
REPORT_DIR.mkdir( parents=True, exist_ok=True)24 β Configure Logging
Section titled β24 β Configure LoggingβAdd:
logging.basicConfig( level=logging.INFO, format=( "%(asctime)s " "%(levelname)s " "%(message)s" ))25 β Define API Configuration
Section titled β25 β Define API ConfigurationβAdd:
BASE_URL = ( "http://127.0.0.1:8080")
DEFAULT_TIMEOUT = 5
MAX_RETRIES = 326 β Protect the Client Token
Section titled β26 β Protect the Client TokenβDo not hard-code the client token.
Use:
API_TOKEN = os.getenv( "SECURITY_API_TOKEN")27 β Configure the Environment Variable
Section titled β27 β Configure the Environment VariableβLinux/macOS:
export SECURITY_API_TOKEN="training-token-123"PowerShell:
$env:SECURITY_API_TOKEN = "training-token-123"This environment variable lasts for the relevant shell/session.
28 β Why Environment Variables?
Section titled β28 β Why Environment Variables?βThey are better than:
API_TOKEN = "secret"because hard-coded secrets can leak into:
GIT
BACKUPS
LOGS
SCREENSHOTS
CODE REVIEWS29 β Environment Variables Are Not Perfect Secret Management
Section titled β29 β Environment Variables Are Not Perfect Secret ManagementβProduction environments should consider:
SECRET MANAGERS
WORKLOAD IDENTITY
MANAGED IDENTITIES
SHORT-LIVED TOKENSThe lab uses an environment variable to teach the fundamental pattern.
30 β Validate Configuration
Section titled β30 β Validate ConfigurationβCreate:
def validate_configuration(): if not API_TOKEN: raise RuntimeError( "SECURITY_API_TOKEN " "environment variable is not set." )31 β Create Headers
Section titled β31 β Create HeadersβCreate:
def build_headers(): return { "Authorization": f"Bearer {API_TOKEN}",
"Accept": "application/json",
"User-Agent": "GoHackersCloud-Security-Lab/1.0" }32 β Why User-Agent?
Section titled β32 β Why User-Agent?βA useful API client should identify itself.
This supports:
LOGGING
TROUBLESHOOTING
AUDITING
API ANALYTICS33 β Build URLs Safely
Section titled β33 β Build URLs SafelyβCreate:
def build_url( endpoint, params=None): url = ( BASE_URL.rstrip("/") + "/" + endpoint.lstrip("/") )
if params: query = urlencode( params )
url = ( f"{url}?{query}" )
return url34 β Why Use urlencode()?
Section titled β34 β Why Use urlencode()?βAvoid manually constructing:
?severity=critical&status=openbecause encoding rules become error-prone.
Use:
STRUCTURED PARAMETERS35 β Build the Request Function
Section titled β35 β Build the Request FunctionβCreate:
def send_get_request( endpoint, params=None, timeout=DEFAULT_TIMEOUT): url = build_url( endpoint, params )
request = Request( url, method="GET" )
for name, value in ( build_headers().items() ): request.add_header( name, value )
with urlopen( request, timeout=timeout ) as response:
return { "status": response.status,
"headers": dict( response.headers ),
"body": response.read() .decode( "utf-8" ) }36 β First API Request
Section titled β36 β First API RequestβTest:
response = send_get_request( "/api/findings")
print(response)37 β Parse JSON
Section titled β37 β Parse JSONβCreate:
def parse_json_response( body): try: return json.loads( body )
except json.JSONDecodeError as error: raise ValueError( "API returned invalid JSON" ) from error38 β Why JSON Validation Matters
Section titled β38 β Why JSON Validation MattersβDo not assume:
Content-Type: application/jsonmeans the body always contains valid JSON.
Systems can return:
HTML ERROR PAGE
EMPTY BODY
TRUNCATED RESPONSE
INVALID JSON39 β Create a Higher-Level API Function
Section titled β39 β Create a Higher-Level API FunctionβCreate:
def get_findings( severity=None, status=None): params = {}
if severity: params[ "severity" ] = severity
if status: params[ "status" ] = status
response = send_get_request( "/api/findings", params=params )
if response[ "status" ] != 200: raise RuntimeError( "Unexpected response status" )
data = parse_json_response( response[ "body" ] )
return data40 β Query Critical Findings
Section titled β40 β Query Critical FindingsβRun:
data = get_findings( severity="critical")
print( json.dumps( data, indent=2 ))Expected:
WEB01
JUMP0141 β Query Open Findings
Section titled β41 β Query Open FindingsβRun:
data = get_findings( status="open")42 β Combine Filters
Section titled β42 β Combine FiltersβRun:
data = get_findings( severity="critical", status="open")43 β Understand Query Parameters
Section titled β43 β Understand Query ParametersβYour client generates:
/api/findings?severity=critical&status=openThis is different from sending filtering instructions in:
HTTP HEADERSor:
REQUEST BODY44 β Handle HTTP Errors
Section titled β44 β Handle HTTP ErrorsβCurrently:
401
404
429
500can raise:
HTTPErrorCreate:
def handle_http_error( error): if error.code == 401: return ( "Authentication failed" )
if error.code == 403: return ( "Request forbidden" )
if error.code == 404: return ( "API endpoint not found" )
if error.code == 429: return ( "API rate limit reached" )
if 500 <= error.code <= 599: return ( "API server error" )
return ( f"HTTP error {error.code}" )45 β Handle Network Errors
Section titled β45 β Handle Network ErrorsβExamples include:
SERVER NOT RUNNING
DNS FAILURE
CONNECTION REFUSED
ROUTING ISSUEThese often appear as:
URLError46 β Build a Safe Request Wrapper
Section titled β46 β Build a Safe Request WrapperβCreate:
def api_get( endpoint, params=None): try: response = send_get_request( endpoint, params=params )
return { "success": True,
"status": response[ "status" ],
"data": parse_json_response( response[ "body" ] ),
"error": None }
except HTTPError as error: message = ( handle_http_error( error ) )
logging.error( message )
return { "success": False,
"status": error.code,
"data": None,
"error": message }
except URLError as error: message = ( f"Network error: " f"{error.reason}" )
logging.error( message )
return { "success": False,
"status": None,
"data": None,
"error": message }
except ValueError as error: logging.error( str(error) )
return { "success": False,
"status": None,
"data": None,
"error": str(error) }47 β Test Wrong Endpoint
Section titled β47 β Test Wrong EndpointβCall:
result = api_get( "/api/not-real")
print(result)Expected:
404with a clean application error rather than a full uncontrolled crash.
48 β Test Server Failure
Section titled β48 β Test Server FailureβStop:
training_api.pyThen run the client.
Expected:
NETWORK ERRORRestart the server afterward.
49 β Why Timeouts Matter
Section titled β49 β Why Timeouts MatterβNever create API automation that waits forever.
Without a timeout:
REQUEST βSERVER NEVER RESPONDS βAUTOMATION HANGSWith a timeout:
REQUEST βWAIT LIMITED TIME βFAIL SAFELY50 β Test Timeout Concept
Section titled β50 β Test Timeout ConceptβYou already configured:
DEFAULT_TIMEOUT = 5Every request should have an explicit timeout.
51 β Understand Retry Logic
Section titled β51 β Understand Retry LogicβSome errors are:
TEMPORARYsuch as:
503 SERVICE UNAVAILABLE
CONNECTION RESET
TRANSIENT NETWORK ISSUEOthers are not good retry candidates:
401 UNAUTHORIZED
403 FORBIDDEN
400 BAD REQUEST52 β Retry Mental Model
Section titled β52 β Retry Mental ModelβREQUEST βTEMPORARY FAILURE? β YESWAIT βRETRY βSUCCESS?53 β Backoff
Section titled β53 β BackoffβAvoid:
FAILβRETRY IMMEDIATELYβFAILβRETRY IMMEDIATELYPrefer:
FAILβWAIT 1 SECONDβFAILβWAIT 2 SECONDSβFAILβWAIT 4 SECONDS54 β Build Retry Logic
Section titled β54 β Build Retry LogicβCreate:
def api_get_with_retry( endpoint, params=None, max_retries=MAX_RETRIES): for attempt in range( max_retries ): result = api_get( endpoint, params=params )
if result[ "success" ]: return result
status = result[ "status" ]
retryable = ( status is None or status == 429 or ( status and 500 <= status <= 599 ) )
if not retryable: return result
if attempt < ( max_retries - 1 ): delay = ( 2 ** attempt )
logging.warning( f"Retrying in " f"{delay} seconds" )
time.sleep( delay )
return result55 β Why Limit Retries?
Section titled β55 β Why Limit Retries?βWithout a limit:
SERVICE FAILURE βAUTOMATION RETRIES FOREVERThis can create:
RESOURCE EXHAUSTION
API PRESSURE
FAILED WORKFLOWS
NOISY LOGGING56 β Rate Limiting
Section titled β56 β Rate LimitingβAPIs may restrict usage to values such as:
100 REQUESTS / MINUTEor:
1,000 REQUESTS / HOUR57 β HTTP 429
Section titled β57 β HTTP 429βA common rate-limit response is:
429 Too Many RequestsThe API may also provide:
Retry-Afteror custom headers.
58 β Rate Limit Mental Model
Section titled β58 β Rate Limit Mental ModelβCLIENT βREQUEST REQUEST REQUEST βAPI LIMIT β429 βWAIT βCONTINUE59 β Do Not Fight Rate Limits
Section titled β59 β Do Not Fight Rate LimitsβDo not design:
429 βOPEN MORE THREADS βSEND MORE REQUESTSRespect provider limits.
60 β Pagination
Section titled β60 β PaginationβLarge APIs usually do not return:
100,000 FINDINGSin one response.
Instead:
PAGE 1
PAGE 2
PAGE 361 β Pagination Model
Section titled β61 β Pagination ModelβREQUEST PAGE 1 βPROCESS βMORE? βREQUEST PAGE 2 βPROCESS62 β Add Pagination to the Training API
Section titled β62 β Add Pagination to the Training APIβModify:
handle_findings()to accept:
page
page_sizeAdd:
page = int( query.get( "page", ["1"] )[0] )
page_size = int( query.get( "page_size", ["2"] )[0] )63 β Calculate Page Boundaries
Section titled β63 β Calculate Page BoundariesβAdd:
start = ( page - 1 ) * page_size
end = ( start + page_size )
paged_results = ( results[ start:end ] )64 β Return Pagination Metadata
Section titled β64 β Return Pagination MetadataβChange response to:
self.send_json( { "page": page,
"page_size": page_size,
"total": len(results),
"results": paged_results } )Restart the server.
65 β Create Pagination Client
Section titled β65 β Create Pagination ClientβCreate:
def get_all_findings( page_size=2): all_findings = []
page = 1
while True: result = ( api_get_with_retry( "/api/findings", params={ "page": page,
"page_size": page_size } ) )
if not result[ "success" ]: raise RuntimeError( result[ "error" ] )
data = result[ "data" ]
records = data[ "results" ]
all_findings.extend( records )
if len( all_findings ) >= data[ "total" ]: break
page += 1
return all_findings66 β Run Pagination
Section titled β66 β Run PaginationβTest:
findings = get_all_findings()
print( len(findings))Expected:
567 β Pagination Safety
Section titled β67 β Pagination SafetyβAlways protect against:
ENDLESS PAGINATIONcaused by buggy APIs.
A mature client can enforce:
MAXIMUM PAGE COUNT68 β Normalize API Findings
Section titled β68 β Normalize API FindingsβDifferent vendors may return different fields.
Vendor A:
{ "machine": "WEB01", "risk": "CRITICAL"}Vendor B:
{ "host": "WEB01", "severity": "critical"}Your internal model should be consistent.
69 β Create an Internal Finding Schema
Section titled β69 β Create an Internal Finding SchemaβUse:
finding_id
asset
finding_type
severity
status
score
source70 β Normalize Training API Records
Section titled β70 β Normalize Training API RecordsβCreate:
def normalize_finding( record): required = { "id", "asset", "type", "severity", "status", "score" }
missing = ( required - set( record.keys() ) )
if missing: raise ValueError( "Missing fields: " + ", ".join( sorted( missing ) ) )
return { "finding_id": str( record[ "id" ] ),
"asset": str( record[ "asset" ] ).strip().upper(),
"finding_type": str( record[ "type" ] ).strip().lower(),
"severity": str( record[ "severity" ] ).strip().lower(),
"status": str( record[ "status" ] ).strip().lower(),
"score": float( record[ "score" ] ),
"source": "training-api" }71 β Validate Score Range
Section titled β71 β Validate Score RangeβImprove:
score = float( record["score"])
if not ( 0.0 <= score <= 10.0): raise ValueError( "Score outside expected range" )72 β Why Normalize API Data?
Section titled β72 β Why Normalize API Data?βWithout normalization:
EACH API=DIFFERENT DATA MODELWith normalization:
MULTIPLE APIS βCOMMON SCHEMA βCENTRAL ANALYTICS73 β Normalize All Findings
Section titled β73 β Normalize All FindingsβCreate:
def normalize_findings( records): valid = [] invalid = []
for record in records: try: valid.append( normalize_finding( record ) )
except ( ValueError, TypeError ) as error:
invalid.append({ "record": record,
"reason": str(error) })
return valid, invalid74 β Count Findings by Severity
Section titled β74 β Count Findings by SeverityβImport:
from collections import CounterCreate:
def count_by_severity( findings): return Counter( item[ "severity" ] for item in findings )75 β Open Findings
Section titled β75 β Open FindingsβCreate:
def get_open_findings( findings): return [ item for item in findings if item[ "status" ] == "open" ]76 β Critical Open Findings
Section titled β76 β Critical Open FindingsβCreate:
def get_critical_open( findings): return [ item for item in findings if ( item[ "status" ] == "open" and item[ "severity" ] == "critical" ) ]77 β Export CSV
Section titled β77 β Export CSVβCreate:
def export_findings_csv( findings): output = ( REPORT_DIR / "api-findings.csv" )
fields = [ "finding_id", "asset", "finding_type", "severity", "status", "score", "source" ]
with output.open( "w", encoding="utf-8", newline="" ) as file:
writer = csv.DictWriter( file, fieldnames=fields )
writer.writeheader()
writer.writerows( findings )78 β Export Invalid Records
Section titled β78 β Export Invalid RecordsβCreate:
def export_invalid_records( records): output = ( REPORT_DIR / "invalid-api-records.json" )
with output.open( "w", encoding="utf-8" ) as file:
json.dump( records, file, indent=2 )79 β Export Raw API Snapshot
Section titled β79 β Export Raw API SnapshotβA useful defensive practice is to preserve the raw source data.
Create:
def export_raw_snapshot( records): output = ( REPORT_DIR / "raw-api-response.json" )
with output.open( "w", encoding="utf-8" ) as file:
json.dump( records, file, indent=2 )80 β Why Preserve Raw Data?
Section titled β80 β Why Preserve Raw Data?βIf normalization behaves incorrectly, you can compare:
RAW API DATAwith:
NORMALIZED OUTPUT81 β Generate Summary JSON
Section titled β81 β Generate Summary JSONβCreate:
def generate_summary( findings): return { "total": len(findings),
"open": len( get_open_findings( findings ) ),
"critical_open": len( get_critical_open( findings ) ),
"severity_counts": dict( count_by_severity( findings ) ) }82 β Save Summary
Section titled β82 β Save SummaryβCreate:
def export_summary( summary): output = ( REPORT_DIR / "api-summary.json" )
with output.open( "w", encoding="utf-8" ) as file:
json.dump( summary, file, indent=2 )83 β Generate Markdown Report
Section titled β83 β Generate Markdown ReportβCreate:
def generate_markdown_report( findings, summary): lines = []
lines.append( "# Security API Integration Report" )
lines.append("")
lines.append( "## Executive Summary" )
lines.append("")
lines.append( f"- Total findings: " f"{summary['total']}" )
lines.append( f"- Open findings: " f"{summary['open']}" )
lines.append( f"- Critical open findings: " f"{summary['critical_open']}" )
lines.append("")
lines.append( "## Critical Open Findings" )
lines.append("")
critical = ( get_critical_open( findings ) )
if critical: for item in critical: lines.append( f"- {item['finding_id']} | " f"{item['asset']} | " f"{item['finding_type']} | " f"Score {item['score']}" )
else: lines.append( "- No critical open findings." )
lines.append("")
lines.append( "## Integration Notes" )
lines.append("")
lines.append( "The client uses a controlled training API. " "Production integrations should use approved " "credentials, least-privilege permissions, " "timeouts, retry controls, rate-limit handling, " "logging, and documented data-retention policies." )
output = ( REPORT_DIR / "security-api-report.md" )
output.write_text( "\n".join( lines ), encoding="utf-8" )84 β Create the Complete Client Workflow
Section titled β84 β Create the Complete Client WorkflowβCreate:
def main(): validate_configuration()
logging.info( "Starting security API collection" )
raw_findings = ( get_all_findings() )
export_raw_snapshot( raw_findings )
findings, invalid = ( normalize_findings( raw_findings ) )
summary = generate_summary( findings )
export_findings_csv( findings )
export_invalid_records( invalid )
export_summary( summary )
generate_markdown_report( findings, summary )
logging.info( "Security API collection complete" )
print( f"Reports saved to: " f"{REPORT_DIR}" )85 β Add Entry Point
Section titled β85 β Add Entry PointβAdd:
if __name__ == "__main__": main()86 β Run the Complete Lab
Section titled β86 β Run the Complete LabβTerminal 1:
python api/training_api.pyTerminal 2:
Linux/macOS:
export SECURITY_API_TOKEN="training-token-123"
python src/security_api_client.pyPowerShell:
$env:SECURITY_API_TOKEN = "training-token-123"
python .\src\security_api_client.py87 β Expected Output
Section titled β87 β Expected OutputβYou should have:
reports/|+-- raw-api-response.json|+-- api-findings.csv|+-- invalid-api-records.json|+-- api-summary.json|+-- security-api-report.md88 β Review the CSV
Section titled β88 β Review the CSVβExpected structure:
finding_id,asset,finding_type,severity,status,score,sourceF-1001,WEB01,vulnerability,critical,open,9.8,training-apiF-1002,DB01,vulnerability,high,open,8.2,training-api89 β API Logging
Section titled β89 β API LoggingβThe client should record:
START
REQUEST FAILURES
RETRIES
NORMALIZATION ERRORS
COMPLETIONDo not log:
BEARER TOKEN90 β Sensitive Header Warning
Section titled β90 β Sensitive Header WarningβNever write:
logging.info( request.headers)when those headers contain:
AUTHORIZATION
API TOKEN
SESSION COOKIE91 β Add Controlled POST Support
Section titled β91 β Add Controlled POST SupportβNow learn a second HTTP method using only your local API.
The goal:
CREATE A SYNTHETICSECURITY NOTEnot perform a production action.
92 β Add a Notes Store to the API
Section titled β92 β Add a Notes Store to the APIβAt the top of:
training_api.pyadd:
NOTES = []93 β Add POST Handling
Section titled β93 β Add POST HandlingβInside the handler:
def do_POST( self ): if not self.is_authorized(): self.send_json( { "error": "unauthorized" }, 401 ) return
parsed = urlparse( self.path )
if parsed.path != "/api/notes": self.send_json( { "error": "not_found" }, 404 ) return
content_length = int( self.headers.get( "Content-Length", "0" ) )
body = self.rfile.read( content_length )
try: data = json.loads( body.decode( "utf-8" ) )
except json.JSONDecodeError: self.send_json( { "error": "invalid_json" }, 400 ) return
note = str( data.get( "note", "" ) ).strip()
if not note: self.send_json( { "error": "note_required" }, 400 ) return
record = { "id": len(NOTES) + 1,
"note": note }
NOTES.append( record )
self.send_json( record, 201 )Restart the server.
94 β Create a POST Client Function
Section titled β94 β Create a POST Client FunctionβIn:
security_api_client.pycreate:
def send_post_request( endpoint, payload, timeout=DEFAULT_TIMEOUT): url = build_url( endpoint )
body = json.dumps( payload ).encode( "utf-8" )
request = Request( url, data=body, method="POST" )
headers = build_headers()
headers[ "Content-Type" ] = "application/json"
for name, value in ( headers.items() ): request.add_header( name, value )
with urlopen( request, timeout=timeout ) as response:
return { "status": response.status,
"body": response.read() .decode( "utf-8" ) }95 β Create a Synthetic Note
Section titled β95 β Create a Synthetic NoteβRun:
result = send_post_request( "/api/notes", { "note": "Analyst reviewed F-1001 " "in the training lab." })
print(result)Expected:
20196 β Why POST Requires More Care
Section titled β96 β Why POST Requires More CareβGET is generally used to:
READ DATAPOST may:
CREATE STATEIn production, writes require stronger controls.
97 β Production Write Workflow
Section titled β97 β Production Write WorkflowβPrefer:
DETECTION βVALIDATE βPREVIEW βHUMAN APPROVAL βAPI WRITE βVERIFY βAUDIT LOG98 β Avoid Autonomous Destructive Actions
Section titled β98 β Avoid Autonomous Destructive ActionsβDo not build:
ALERT βAPI βDELETE ACCOUNTor:
IOC MATCH βAPI βBLOCK EVERYTHINGwithout appropriate governance and controls.
99 β Least-Privilege API Tokens
Section titled β99 β Least-Privilege API TokensβPrefer separate permissions.
Example:
REPORTING TOKEN βREAD FINDINGSinstead of:
ADMIN TOKEN βEVERYTHING100 β API Scope Mental Model
Section titled β100 β API Scope Mental ModelβAUTOMATION PURPOSE βREQUIRED ENDPOINTS βREQUIRED METHODS βMINIMUM PERMISSIONS βTOKEN SCOPE101 β Token Rotation
Section titled β101 β Token RotationβProduction tokens should support:
ROTATION
EXPIRATION
REVOCATIONLong-lived permanent credentials increase risk.
102 β Workload Identity
Section titled β102 β Workload IdentityβCloud environments may avoid static API secrets entirely.
Conceptually:
AUTOMATION WORKLOAD βPLATFORM IDENTITY βSHORT-LIVED CREDENTIAL βAPI103 β TLS
Section titled β103 β TLSβProduction APIs should generally use:
HTTPSYour lab uses:
HTTPonly because the API runs locally on:
127.0.0.1104 β Never Disable TLS Verification Casually
Section titled β104 β Never Disable TLS Verification CasuallyβDo not normalize practices such as:
VERIFY TLS = FALSEagainst production APIs.
Certificate verification protects against:
SERVER IMPERSONATION
MAN-IN-THE-MIDDLE RISK105 β API Data Classification
Section titled β105 β API Data ClassificationβBefore integration, determine whether API data contains:
USER DATA
INTERNAL HOSTNAMES
IP ADDRESSES
VULNERABILITIES
SECURITY ALERTS
INCIDENT INFORMATION
CREDENTIAL METADATA106 β Data Minimization
Section titled β106 β Data MinimizationβIf the report requires:
finding_id
asset
severitydo not collect:
50 EXTRA FIELDSwithout a reason.
107 β Logging Correlation ID
Section titled β107 β Logging Correlation IDβA mature API may return:
X-Request-IDor similar.
Store such identifiers in logs so failures can be traced.
108 β Client Request ID
Section titled β108 β Client Request IDβYou can also generate your own internal ID.
Import:
import uuidCreate:
request_id = str( uuid.uuid4())Then log:
logging.info( "request_id=%s starting request", request_id)109 β Observability
Section titled β109 β ObservabilityβUseful API automation metrics include:
REQUEST COUNT
SUCCESS RATE
ERROR RATE
401 COUNT
429 COUNT
5XX COUNT
AVERAGE RESPONSE TIME
RETRY COUNT
RECORDS PROCESSED110 β Measure Response Time
Section titled β110 β Measure Response TimeβImport:
from time import perf_counterConcept:
start = perf_counter()
result = api_get( "/api/findings")
duration = ( perf_counter() - start)
logging.info( "Request duration: %.3f seconds", duration)111 β Cache API Results
Section titled β111 β Cache API ResultsβRepeated enrichment requests may benefit from:
CACHEArchitecture:
REQUEST DATA βCACHE HIT? ββββ΄βββ YES NO β βUSE APICACHE β SAVE112 β Cache Security Consideration
Section titled β112 β Cache Security ConsiderationβCached API data may itself be sensitive.
Protect it with:
ACCESS CONTROL
RETENTION
ENCRYPTION WHERE REQUIRED113 β Do Not Cache Forever
Section titled β113 β Do Not Cache ForeverβSecurity data can become stale.
Use:
CACHE TIMESTAMP
TTL
EXPIRATION114 β Idempotency
Section titled β114 β IdempotencyβAn important API concept:
SAME REQUESTEXECUTED MULTIPLE TIMESshould not unexpectedly create repeated state where idempotency is required.
This is especially important for:
TICKET CREATION
FIREWALL CHANGES
ACCOUNT CHANGES
CASE CREATION115 β Deduplication Before POST
Section titled β115 β Deduplication Before POSTβSuppose automation creates incidents.
Avoid:
SAME ALERT β5 RETRIES β5 INCIDENTSUse:
EVENT ID
IDEMPOTENCY KEY
EXISTING CASE CHECK116 β API Versioning
Section titled β116 β API VersioningβProduction APIs often use paths such as:
/api/v1/findingsor:
/api/v2/findingsDo not assume API structures never change.
117 β Schema Changes
Section titled β117 β Schema ChangesβA vendor may change:
riskto:
severityYour normalization layer helps isolate those changes.
118 β Adapter Pattern
Section titled β118 β Adapter PatternβConceptually:
VENDOR A API βADAPTER A βCOMMON MODEL
VENDOR B API βADAPTER B βCOMMON MODEL119 β Why Adapters Matter
Section titled β119 β Why Adapters MatterβYour reporting layer should not care whether data came from:
SIEM A
SIEM B
CLOUD SECURITY TOOL
VULNERABILITY TOOLIt should receive:
NORMALIZED SECURITY DATA120 β Build a Security API Pipeline
Section titled β120 β Build a Security API PipelineβFinal mental architecture:
SOURCE API βAUTHENTICATE βREQUEST βSTATUS VALIDATION βJSON VALIDATION βSCHEMA VALIDATION βNORMALIZATION βDEDUPLICATION βANALYSIS βREPORT121 β Test Case: Missing Token
Section titled β121 β Test Case: Missing TokenβRemove:
SECURITY_API_TOKENRun the client.
Expected:
CONFIGURATION ERRORbefore API requests are sent.
122 β Test Case: Wrong Token
Section titled β122 β Test Case: Wrong TokenβSet:
wrong-tokenExpected:
401 Unauthorized123 β Test Case: Server Offline
Section titled β123 β Test Case: Server OfflineβStop the server.
Expected:
NETWORK ERROR
RETRIES
FINAL FAILURE124 β Test Case: Invalid Endpoint
Section titled β124 β Test Case: Invalid EndpointβRequest:
/api/unknownExpected:
404and:
NO RETRY125 β Test Case: Invalid JSON
Section titled β125 β Test Case: Invalid JSONβTemporarily modify the training API to return invalid text.
Confirm:
JSON VALIDATION FAILSThen restore the valid server.
126 β Test Case: Missing Field
Section titled β126 β Test Case: Missing FieldβRemove:
severityfrom one synthetic API finding.
Confirm that normalization sends the record to:
INVALID RECORDS127 β Test Case: Bad Score
Section titled β127 β Test Case: Bad ScoreβSet:
score = "invalid"Confirm the client does not crash.
128 β Test Case: Pagination
Section titled β128 β Test Case: PaginationβSet:
page_size = 1Confirm:
ALL 5 RECORDSare still collected.
129 β Test Case: Duplicate API Records
Section titled β129 β Test Case: Duplicate API RecordsβAdd a duplicate finding to the server.
Then extend your client to deduplicate using:
finding_id130 β Add Deduplication
Section titled β130 β Add DeduplicationβCreate:
def deduplicate_findings( findings): unique = [] duplicates = []
seen = set()
for finding in findings: key = finding[ "finding_id" ]
if key in seen: duplicates.append( finding ) continue
seen.add( key )
unique.append( finding )
return unique, duplicates131 β Why Deduplicate API Data?
Section titled β131 β Why Deduplicate API Data?βDuplicate results can occur because of:
PAGINATION CHANGES
API BUGS
REPEATED IMPORT
EVENTUAL CONSISTENCY
RETRY LOGIC132 β Create a Test Suite
Section titled β132 β Create a Test SuiteβCreate:
tests/test_security_api_client.py133 β Test URL Building
Section titled β133 β Test URL Buildingβdef test_build_url(): url = build_url( "/api/findings", { "severity": "critical" } )
assert ( "severity=critical" in url )134 β Test Normalization
Section titled β134 β Test Normalizationβdef test_normalize_finding(): record = { "id": "F-1", "asset": "web01", "type": "Vulnerability", "severity": "CRITICAL", "status": "OPEN", "score": 9.8 }
normalized = ( normalize_finding( record ) )
assert ( normalized[ "asset" ] == "WEB01" )
assert ( normalized[ "severity" ] == "critical" )135 β Test Missing Fields
Section titled β135 β Test Missing Fieldsβdef test_missing_field(): record = { "id": "F-1" }
try: normalize_finding( record )
except ValueError: assert True
else: assert False136 β Test Score Range
Section titled β136 β Test Score RangeβA score:
15.0should be rejected when the expected range is:
0β10137 β Test Deduplication
Section titled β137 β Test DeduplicationβProvide:
F-1001
F-1001Expected:
UNIQUE = 1
DUPLICATE = 1138 β Challenge 01 β Add API Health Check
Section titled β138 β Challenge 01 β Add API Health CheckβBefore collecting:
FINDINGScall:
/api/healthIf unavailable:
STOP CLEANLY139 β Challenge 02 β Add Config File
Section titled β139 β Challenge 02 β Add Config FileβInstead of hard-coding:
BASE_URLuse:
data/config.jsonwith no secrets inside it.
140 β Challenge 03 β Add CLI Parameters
Section titled β140 β Challenge 03 β Add CLI ParametersβUse:
argparseto support:
--base-url
--status
--severity
--output141 β Challenge 04 β Add Structured Logs
Section titled β141 β Challenge 04 β Add Structured LogsβInstead of human-only logs:
2026... ERROR ...produce optional:
{ "level": "error", "event": "api_request_failed", "status": 503}142 β Challenge 05 β Add Local Cache
Section titled β142 β Challenge 05 β Add Local CacheβCache:
/api/findingsfor:
60 secondsThen compare:
API REQUEST COUNTbefore and after caching.
143 β Challenge 06 β Add Retry-After Support
Section titled β143 β Challenge 06 β Add Retry-After SupportβWhen:
429is returned, inspect:
Retry-Afterand wait accordingly.
144 β Challenge 07 β Add Metrics
Section titled β144 β Challenge 07 β Add MetricsβProduce:
api-metrics.jsoncontaining:
requests
successes
failures
retries
records_processed
duration145 β Challenge 08 β Add SQLite Storage
Section titled β145 β Challenge 08 β Add SQLite StorageβArchitecture:
API βPYTHON βNORMALIZE βSQLITE βSQL ANALYTICSThis connects:
Lab 05+Lab 07146 β Challenge 09 β Combine Vulnerability Prioritization
Section titled β146 β Challenge 09 β Combine Vulnerability PrioritizationβFeed API results into the prioritization logic from:
Lab 06Architecture:
SECURITY API βFINDINGS βNORMALIZE βASSET CONTEXT βRISK PRIORITY βREMEDIATION QUEUE147 β Challenge 10 β Add Human Approval
Section titled β147 β Challenge 10 β Add Human ApprovalβFor any simulated write operation:
PREVIEW ACTION βASK FOR APPROVAL βPOST βVERIFY RESPONSE148 β API Security Checklist
Section titled β148 β API Security ChecklistβBefore integrating a real security API, review:
- API endpoint approved
- HTTPS used
- TLS verification enabled
- Authentication method understood
- Least-privilege token used
- Secrets not hard-coded
- Token rotation supported
- Timeouts configured
- Errors handled
- Retries limited
- Backoff implemented
- 429 handling understood
- Pagination implemented
- JSON validated
- Schema validated
- Data normalized
- Duplicate handling implemented
- Sensitive fields minimized
- Logs exclude secrets
- API writes governed
- Response verified
- Data retention understood
149 β Common Security API Integration Mistakes
Section titled β149 β Common Security API Integration MistakesβAvoid:
HARDCODED API KEYS
NO TIMEOUT
INFINITE RETRIES
RETRYING 401 ERRORS
IGNORING 429
NO PAGINATION
TRUSTING JSON BLINDLY
NO SCHEMA VALIDATION
NO NORMALIZATION
LOGGING AUTHORIZATION HEADERS
COLLECTING TOO MUCH DATA
USING ADMIN TOKENS FOR READ-ONLY JOBS
AUTOMATICALLY EXECUTING HIGH-IMPACT ACTIONS
NO AUDIT LOG
NO RESPONSE VERIFICATION150 β Production Security Integration Principle
Section titled β150 β Production Security Integration PrincipleβDo not think:
API=JUST ANOTHER DATA SOURCEAn API can be:
A PRIVILEGED CONTROL PLANEIt may be able to:
DISABLE ACCOUNTS
ISOLATE ENDPOINTS
CHANGE FIREWALLS
DELETE RESOURCES
CREATE INCIDENTS
MODIFY SECURITY POLICIESThat means API permissions require serious governance.
151 β Read Before Write
Section titled β151 β Read Before WriteβA strong security automation principle is:
READ βUNDERSTAND βVALIDATE βPREVIEW βAPPROVE βWRITE βVERIFY152 β API Failure Mental Model
Section titled β152 β API Failure Mental ModelβWhen an API request fails:
WHAT FAILED? βNETWORK? βAUTH? βPERMISSION? βRATE LIMIT? βSERVER? βBAD REQUEST? βBAD DATA?Do not treat every failure identically.
153 β Security Automation Mental Model
Section titled β153 β Security Automation Mental ModelβREQUEST βTIMEOUT βSTATUS βHEADERS βBODY βJSON βSCHEMA βNORMALIZE βANALYZE154 β Build Your Final Portfolio Structure
Section titled β154 β Build Your Final Portfolio StructureβYour completed project should look like:
security-api-integration/|+-- api/| +-- training_api.py|+-- data/|+-- src/| +-- security_api_client.py|+-- tests/| +-- test_security_api_client.py|+-- reports/| +-- raw-api-response.json| +-- api-findings.csv| +-- invalid-api-records.json| +-- api-summary.json| +-- security-api-report.md|+-- README.md|+-- architecture.md155 β README Structure
Section titled β155 β README StructureβInclude:
PROJECT OVERVIEW
SECURITY USE CASE
ARCHITECTURE
TRAINING API
AUTHENTICATION
ENVIRONMENT VARIABLES
HTTP METHODS
ERROR HANDLING
RETRY STRATEGY
RATE LIMITING
PAGINATION
NORMALIZATION
REPORTS
TESTING
SECURITY CONSIDERATIONS
LIMITATIONS156 β Document Limitations
Section titled β156 β Document LimitationsβYour local training API does not provide:
REAL TLS
OAUTH 2.0
SHORT-LIVED TOKENS
ENTERPRISE RATE LIMITS
DISTRIBUTED PAGINATION
WEBHOOKS
PRODUCTION SECURITY DATA
HIGH AVAILABILITY
REAL RBACThese are future learning areas.
157 β Mission Validation Checklist
Section titled β157 β Mission Validation ChecklistβConfirm:
- Lab workspace created
- Local training API created
- Synthetic findings created
- Bearer authentication implemented
- Health endpoint tested
- Unauthorized request tested
- GET request completed
- Query parameters used
- HTTP status codes reviewed
- JSON parsed
- JSON validation implemented
- Client token moved to environment variable
- Timeout configured
- HTTP errors handled
- Network errors handled
- Retry logic implemented
- Backoff implemented
- Rate limiting understood
- Pagination implemented
- All paginated records collected
- API findings normalized
- Missing fields rejected
- Invalid scores rejected
- Duplicate logic implemented
- Raw API snapshot exported
- Normalized CSV exported
- Invalid records exported
- Summary JSON exported
- Markdown report generated
- Controlled local POST tested
- No real credentials used
- Authorization headers excluded from logs
- No destructive API automation created
- README created
- Limitations documented
Mission Review
Section titled βMission ReviewβYou started with:
SECURITY DATAINSIDE AN APIYou built:
PYTHON CLIENT βAUTHENTICATION βGET REQUEST βTIMEOUT βSTATUS VALIDATION βJSON PARSING βSCHEMA VALIDATION βNORMALIZATION βPAGINATION βRETRY / BACKOFF βSECURITY ANALYSIS βREPORTWhat You Built
Section titled βWhat You BuiltβYou now have a reusable security API integration pattern capable of handling:
AUTHENTICATION
QUERY PARAMETERS
JSON RESPONSES
STATUS CODES
TIMEOUTS
ERRORS
RETRIES
RATE LIMITS
PAGINATION
NORMALIZATION
CSV / JSON REPORTINGKey Security Lesson
Section titled βKey Security LessonβThe central lesson is:
A SUCCESSFUL HTTP REQUESTDOES NOT AUTOMATICALLY MEANA SUCCESSFUL SECURITY WORKFLOWYou must validate:
STATUS
CONTENT
SCHEMA
DATA QUALITY
AUTHORIZATION
CONTEXTFinal Mental Model
Section titled βFinal Mental ModelβWhenever you integrate with a security API, think:
WHAT API AM I CALLING? βWHAT PERMISSIONS DO I NEED? βHOW WILL I AUTHENTICATE? βIS THE CONNECTION TRUSTED? βWHAT IS MY TIMEOUT? βWHAT IF IT FAILS? βSHOULD I RETRY? βIS THERE A RATE LIMIT? βIS THERE PAGINATION? βIS THE JSON VALID? βIS THE SCHEMA VALID? βHOW WILL I NORMALIZE IT? βWHAT SECURITY DECISIONWILL USE THIS DATA?The goal is not:
CALL MORE APISThe goal is:
BUILD RELIABLE,CONTROLLED,AUDITABLESECURITY INTEGRATIONSWhatβs Next?
Section titled βWhatβs Next?ββ‘οΈ Lab 08 β Cloud Security Configuration Auditor
The next lab takes your Python automation and API skills into cloud security.
You will build a read-only cloud security assessment workflow for an authorized lab account.
The architecture will be:
CLOUD ACCOUNT βCLOUD API βSECURITY AUDITOR βIDENTITY REVIEW βLOGGING REVIEW βENCRYPTION REVIEW βPUBLIC EXPOSURE REVIEW βSECURITY CONTROL REVIEW βNORMALIZED FINDINGS βSECURITY REPORTYou will learn how to turn cloud configuration data into repeatable security findings without making disruptive changes to cloud resources.