# Code Examples ## Setup and Authentication ### Basic Setup ```python import os import requests from dotenv import load_dotenv # Load environment variables load_dotenv() # Configuration API_KEY = os.getenv("ADAPTYV_API_KEY") BASE_URL = "https://kq5jp7qj7wdqklhsxmovkzn4l40obksv.lambda-url.eu-central-1.on.aws" # Standard headers HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } def check_api_connection(): """Verify API connection and credentials""" try: response = requests.get(f"{BASE_URL}/organization/credits", headers=HEADERS) response.raise_for_status() print("✓ API connection successful") print(f" Credits remaining: {response.json()['balance']}") return True except requests.exceptions.HTTPError as e: print(f"✗ API authentication failed: {e}") return False ``` ### Environment Setup Create a `.env` file: ```bash ADAPTYV_API_KEY=your_api_key_here ``` Install dependencies: ```bash uv pip install requests python-dotenv ``` ## Experiment Submission ### Submit Single Sequence ```python def submit_single_experiment(sequence, experiment_type="binding", target_id=None): """ Submit a single protein sequence for testing Args: sequence: Amino acid sequence string experiment_type: Type of experiment (binding, expression, thermostability, enzyme_activity) target_id: Optional target identifier for binding assays Returns: Experiment ID and status """ # Format as FASTA fasta_content = f">protein_sequence\n{sequence}\n" payload = { "sequences": fasta_content, "experiment_type": experiment_type } if target_id: payload["target_id"] = target_id response = requests.post( f"{BASE_URL}/experiments", headers=HEADERS, json=payload ) response.raise_for_status() result = response.json() print(f"✓ Experiment submitted") print(f" Experiment ID: {result['experiment_id']}") print(f" Status: {result['status']}") print(f" Estimated completion: {result['estimated_completion']}") return result # Example usage sequence = "MKVLWAALLGLLGAAAAFPAVTSAVKPYKAAVSAAVSKPYKAAVSAAVSKPYK" experiment = submit_single_experiment(sequence, experiment_type="expression") ``` ### Submit Multiple Sequences (Batch) ```python def submit_batch_experiment(sequences_dict, experiment_type="binding", metadata=None): """ Submit multiple protein sequences in a single batch Args: sequences_dict: Dictionary of {name: sequence} experiment_type: Type of experiment metadata: Optional dictionary of additional information Returns: Experiment details """ # Format all sequences as FASTA fasta_content = "" for name, sequence in sequences_dict.items(): fasta_content += f">{name}\n{sequence}\n" payload = { "sequences": fasta_content, "experiment_type": experiment_type } if metadata: payload["metadata"] = metadata response = requests.post( f"{BASE_URL}/experiments", headers=HEADERS, json=payload ) response.raise_for_status() result = response.json() print(f"✓ Batch experiment submitted") print(f" Experiment ID: {result['experiment_id']}") print(f" Sequences: {len(sequences_dict)}") print(f" Status: {result['status']}") return result # Example usage sequences = { "variant_1": "MKVLWAALLGLLGAAA...", "variant_2": "MKVLSAALLGLLGAAA...", "variant_3": "MKVLAAALLGLLGAAA...", "wildtype": "MKVLWAALLGLLGAAA..." } metadata = { "project": "antibody_optimization", "round": 3, "notes": "Testing solubility-optimized variants" } experiment = submit_batch_experiment(sequences, "expression", metadata) ``` ### Submit with Webhook Notification ```python def submit_with_webhook(sequences_dict, experiment_type, webhook_url): """ Submit experiment with webhook for completion notification Args: sequences_dict: Dictionary of {name: sequence} experiment_type: Type of experiment webhook_url: URL to receive notification when complete """ fasta_content = "" for name, sequence in sequences_dict.items(): fasta_content += f">{name}\n{sequence}\n" payload = { "sequences": fasta_content, "experiment_type": experiment_type, "webhook_url": webhook_url } response = requests.post( f"{BASE_URL}/experiments", headers=HEADERS, json=payload ) response.raise_for_status() result = response.json() print(f"✓ Experiment submitted with webhook") print(f" Experiment ID: {result['experiment_id']}") print(f" Webhook: {webhook_url}") return result # Example webhook_url = "https://your-server.com/adaptyv-webhook" experiment = submit_with_webhook(sequences, "binding", webhook_url) ``` ## Tracking Experiments ### Check Experiment Status ```python def check_experiment_status(experiment_id): """ Get current status of an experiment Args: experiment_id: Experiment identifier Returns: Status information """ response = requests.get( f"{BASE_URL}/experiments/{experiment_id}", headers=HEADERS ) response.raise_for_status() status = response.json() print(f"Experiment: {experiment_id}") print(f" Status: {status['status']}") print(f" Created: {status['created_at']}") print(f" Updated: {status['updated_at']}") if 'progress' in status: print(f" Progress: {status['progress']['percentage']}%") print(f" Current stage: {status['progress']['stage']}") return status # Example status = check_experiment_status("exp_abc123xyz") ``` ### List All Experiments ```python def list_experiments(status_filter=None, limit=50): """ List experiments with optional status filtering Args: status_filter: Filter by status (submitted, processing, completed, failed) limit: Maximum number of results Returns: List of experiments """ params = {"limit": limit} if status_filter: params["status"] = status_filter response = requests.get( f"{BASE_URL}/experiments", headers=HEADERS, params=params ) response.raise_for_status() result = response.json() print(f"Found {result['total']} experiments") for exp in result['experiments']: print(f" {exp['experiment_id']}: {exp['status']} ({exp['experiment_type']})") return result['experiments'] # Example - list all completed experiments completed_experiments = list_experiments(status_filter="completed") ``` ### Poll Until Complete ```python import time def wait_for_completion(experiment_id, check_interval=3600): """ Poll experiment status until completion Args: experiment_id: Experiment identifier check_interval: Seconds between status checks (default: 1 hour) Returns: Final status """ print(f"Monitoring experiment {experiment_id}...") while True: status = check_experiment_status(experiment_id) if status['status'] == 'completed': print("✓ Experiment completed!") return status elif status['status'] == 'failed': print("✗ Experiment failed") return status print(f" Status: {status['status']} - checking again in {check_interval}s") time.sleep(check_interval) # Example (not recommended - use webhooks instead!) # status = wait_for_completion("exp_abc123xyz", check_interval=3600) ``` ## Retrieving Results ### Download Experiment Results ```python import json def download_results(experiment_id, output_dir="results"): """ Download and parse experiment results Args: experiment_id: Experiment identifier output_dir: Directory to save results Returns: Parsed results data """ # Get results response = requests.get( f"{BASE_URL}/experiments/{experiment_id}/results", headers=HEADERS ) response.raise_for_status() results = response.json() # Save results JSON os.makedirs(output_dir, exist_ok=True) output_file = f"{output_dir}/{experiment_id}_results.json" with open(output_file, 'w') as f: json.dump(results, f, indent=2) print(f"✓ Results downloaded: {output_file}") print(f" Sequences tested: {len(results['results'])}") # Download raw data if available if 'download_urls' in results: for data_type, url in results['download_urls'].items(): print(f" {data_type} available at: {url}") return results # Example results = download_results("exp_abc123xyz") ``` ### Parse Binding Results ```python import pandas as pd def parse_binding_results(results): """ Parse binding assay results into DataFrame Args: results: Results dictionary from API Returns: pandas DataFrame with organized results """ data = [] for result in results['results']: row = { 'sequence_id': result['sequence_id'], 'kd': result['measurements']['kd'], 'kd_error': result['measurements']['kd_error'], 'kon': result['measurements']['kon'], 'koff': result['measurements']['koff'], 'confidence': result['quality_metrics']['confidence'], 'r_squared': result['quality_metrics']['r_squared'] } data.append(row) df = pd.DataFrame(data) # Sort by affinity (lower KD = stronger binding) df = df.sort_values('kd') print("Top 5 binders:") print(df.head()) return df # Example experiment_id = "exp_abc123xyz" results = download_results(experiment_id) binding_df = parse_binding_results(results) # Export to CSV binding_df.to_csv(f"{experiment_id}_binding_results.csv", index=False) ``` ### Parse Expression Results ```python def parse_expression_results(results): """ Parse expression testing results into DataFrame Args: results: Results dictionary from API Returns: pandas DataFrame with organized results """ data = [] for result in results['results']: row = { 'sequence_id': result['sequence_id'], 'yield_mg_per_l': result['measurements']['total_yield_mg_per_l'], 'soluble_fraction': result['measurements']['soluble_fraction_percent'], 'purity': result['measurements']['purity_percent'], 'percentile': result['ranking']['percentile'] } data.append(row) df = pd.DataFrame(data) # Sort by yield df = df.sort_values('yield_mg_per_l', ascending=False) print(f"Mean yield: {df['yield_mg_per_l'].mean():.2f} mg/L") print(f"Top performer: {df.iloc[0]['sequence_id']} ({df.iloc[0]['yield_mg_per_l']:.2f} mg/L)") return df # Example results = download_results("exp_expression123") expression_df = parse_expression_results(results) ``` ## Target Catalog ### Search for Targets ```python def search_targets(query, species=None, category=None): """ Search the antigen catalog Args: query: Search term (protein name, UniProt ID, etc.) species: Optional species filter category: Optional category filter Returns: List of matching targets """ params = {"search": query} if species: params["species"] = species if category: params["category"] = category response = requests.get( f"{BASE_URL}/targets", headers=HEADERS, params=params ) response.raise_for_status() targets = response.json()['targets'] print(f"Found {len(targets)} targets matching '{query}':") for target in targets: print(f" {target['target_id']}: {target['name']}") print(f" Species: {target['species']}") print(f" Availability: {target['availability']}") print(f" Price: ${target['price_usd']}") return targets # Example targets = search_targets("PD-L1", species="Homo sapiens") ``` ### Request Custom Target ```python def request_custom_target(target_name, uniprot_id=None, species=None, notes=None): """ Request a custom antigen not in the standard catalog Args: target_name: Name of the target protein uniprot_id: Optional UniProt identifier species: Species name notes: Additional requirements or notes Returns: Request confirmation """ payload = { "target_name": target_name, "species": species } if uniprot_id: payload["uniprot_id"] = uniprot_id if notes: payload["notes"] = notes response = requests.post( f"{BASE_URL}/targets/request", headers=HEADERS, json=payload ) response.raise_for_status() result = response.json() print(f"✓ Custom target request submitted") print(f" Request ID: {result['request_id']}") print(f" Status: {result['status']}") return result # Example request = request_custom_target( target_name="Novel receptor XYZ", uniprot_id="P12345", species="Mus musculus", notes="Need high purity for structural studies" ) ``` ## Complete Workflows ### End-to-End Binding Assay ```python def complete_binding_workflow(sequences_dict, target_id, project_name): """ Complete workflow: submit sequences, track, and retrieve binding results Args: sequences_dict: Dictionary of {name: sequence} target_id: Target identifier from catalog project_name: Project name for metadata Returns: DataFrame with binding results """ print("=== Starting Binding Assay Workflow ===") # Step 1: Submit experiment print("\n1. Submitting experiment...") metadata = { "project": project_name, "target": target_id } experiment = submit_batch_experiment( sequences_dict, experiment_type="binding", metadata=metadata ) experiment_id = experiment['experiment_id'] # Step 2: Save experiment info print("\n2. Saving experiment details...") with open(f"{experiment_id}_info.json", 'w') as f: json.dump(experiment, f, indent=2) print(f"✓ Experiment {experiment_id} submitted") print(" Results will be available in ~21 days") print(" Use webhook or poll status for updates") # Note: In practice, wait for completion before this step # print("\n3. Waiting for completion...") # status = wait_for_completion(experiment_id) # print("\n4. Downloading results...") # results = download_results(experiment_id) # print("\n5. Parsing results...") # df = parse_binding_results(results) # return df return experiment_id # Example antibody_variants = { "variant_1": "EVQLVESGGGLVQPGG...", "variant_2": "EVQLVESGGGLVQPGS...", "variant_3": "EVQLVESGGGLVQPGA...", "wildtype": "EVQLVESGGGLVQPGG..." } experiment_id = complete_binding_workflow( antibody_variants, target_id="tgt_pdl1_human", project_name="antibody_affinity_maturation" ) ``` ### Optimization + Testing Pipeline ```python # Combine computational optimization with experimental testing def optimization_and_testing_pipeline(initial_sequences, experiment_type="expression"): """ Complete pipeline: optimize sequences computationally, then submit for testing Args: initial_sequences: Dictionary of {name: sequence} experiment_type: Type of experiment Returns: Experiment ID for tracking """ print("=== Optimization and Testing Pipeline ===") # Step 1: Computational optimization print("\n1. Computational optimization...") from protein_optimization import complete_optimization_pipeline optimized = complete_optimization_pipeline(initial_sequences) print(f"✓ Optimization complete") print(f" Started with: {len(initial_sequences)} sequences") print(f" Optimized to: {len(optimized)} sequences") # Step 2: Select top candidates print("\n2. Selecting top candidates for testing...") top_candidates = optimized[:50] # Top 50 sequences_to_test = { seq_data['name']: seq_data['sequence'] for seq_data in top_candidates } # Step 3: Submit for experimental validation print("\n3. Submitting to Adaptyv...") metadata = { "optimization_method": "computational_pipeline", "initial_library_size": len(initial_sequences), "computational_scores": [s['combined'] for s in top_candidates] } experiment = submit_batch_experiment( sequences_to_test, experiment_type=experiment_type, metadata=metadata ) print(f"✓ Pipeline complete") print(f" Experiment ID: {experiment['experiment_id']}") return experiment['experiment_id'] # Example initial_library = { f"variant_{i}": generate_random_sequence() for i in range(1000) } experiment_id = optimization_and_testing_pipeline( initial_library, experiment_type="expression" ) ``` ### Batch Result Analysis ```python def analyze_multiple_experiments(experiment_ids): """ Download and analyze results from multiple experiments Args: experiment_ids: List of experiment identifiers Returns: Combined DataFrame with all results """ all_results = [] for exp_id in experiment_ids: print(f"Processing {exp_id}...") # Download results results = download_results(exp_id, output_dir=f"results/{exp_id}") # Parse based on experiment type exp_type = results.get('experiment_type', 'unknown') if exp_type == 'binding': df = parse_binding_results(results) df['experiment_id'] = exp_id all_results.append(df) elif exp_type == 'expression': df = parse_expression_results(results) df['experiment_id'] = exp_id all_results.append(df) # Combine all results combined_df = pd.concat(all_results, ignore_index=True) print(f"\n✓ Analysis complete") print(f" Total experiments: {len(experiment_ids)}") print(f" Total sequences: {len(combined_df)}") return combined_df # Example experiment_ids = [ "exp_round1_abc", "exp_round2_def", "exp_round3_ghi" ] all_data = analyze_multiple_experiments(experiment_ids) all_data.to_csv("combined_results.csv", index=False) ``` ## Error Handling ### Robust API Wrapper ```python import time from requests.exceptions import RequestException, HTTPError def api_request_with_retry(method, url, max_retries=3, backoff_factor=2, **kwargs): """ Make API request with retry logic and error handling Args: method: HTTP method (GET, POST, etc.) url: Request URL max_retries: Maximum number of retry attempts backoff_factor: Exponential backoff multiplier **kwargs: Additional arguments for requests Returns: Response object Raises: RequestException: If all retries fail """ for attempt in range(max_retries): try: response = requests.request(method, url, **kwargs) response.raise_for_status() return response except HTTPError as e: if e.response.status_code == 429: # Rate limit wait_time = backoff_factor ** attempt print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) continue elif e.response.status_code >= 500: # Server error if attempt < max_retries - 1: wait_time = backoff_factor ** attempt print(f"Server error. Retrying in {wait_time}s...") time.sleep(wait_time) continue else: raise else: # Client error (4xx) - don't retry error_data = e.response.json() if e.response.content else {} print(f"API Error: {error_data.get('error', {}).get('message', str(e))}") raise except RequestException as e: if attempt < max_retries - 1: wait_time = backoff_factor ** attempt print(f"Request failed. Retrying in {wait_time}s...") time.sleep(wait_time) continue else: raise raise RequestException(f"Failed after {max_retries} attempts") # Example usage response = api_request_with_retry( "POST", f"{BASE_URL}/experiments", headers=HEADERS, json={"sequences": fasta_content, "experiment_type": "binding"} ) ``` ## Utility Functions ### Validate FASTA Format ```python def validate_fasta(fasta_string): """ Validate FASTA format and sequences Args: fasta_string: FASTA-formatted string Returns: Tuple of (is_valid, error_message) """ lines = fasta_string.strip().split('\n') if not lines: return False, "Empty FASTA content" if not lines[0].startswith('>'): return False, "FASTA must start with header line (>)" valid_amino_acids = set("ACDEFGHIKLMNPQRSTVWY") current_header = None for i, line in enumerate(lines): if line.startswith('>'): if not line[1:].strip(): return False, f"Line {i+1}: Empty header" current_header = line[1:].strip() else: if current_header is None: return False, f"Line {i+1}: Sequence before header" sequence = line.strip().upper() invalid = set(sequence) - valid_amino_acids if invalid: return False, f"Line {i+1}: Invalid amino acids: {invalid}" return True, None # Example fasta = ">protein1\nMKVLWAALLG\n>protein2\nMATGVLWALG" is_valid, error = validate_fasta(fasta) if is_valid: print("✓ FASTA format valid") else: print(f"✗ FASTA validation failed: {error}") ``` ### Format Sequences to FASTA ```python def sequences_to_fasta(sequences_dict): """ Convert dictionary of sequences to FASTA format Args: sequences_dict: Dictionary of {name: sequence} Returns: FASTA-formatted string """ fasta_content = "" for name, sequence in sequences_dict.items(): # Clean sequence (remove whitespace, ensure uppercase) clean_seq = ''.join(sequence.split()).upper() # Validate is_valid, error = validate_fasta(f">{name}\n{clean_seq}") if not is_valid: raise ValueError(f"Invalid sequence '{name}': {error}") fasta_content += f">{name}\n{clean_seq}\n" return fasta_content # Example sequences = { "var1": "MKVLWAALLG", "var2": "MATGVLWALG" } fasta = sequences_to_fasta(sequences) print(fasta) ```