# Custom Requests Guide This guide covers making advanced API calls using the TrustCafé API wrapper's direct request functionality. This is for when jobs or wrappers don't provide the level of control you need. ## Table of Contents - [Overview](#overview) - [Basic Usage](#basic-usage) - [Authentication](#authentication) - [Common Patterns](#common-patterns) - [Advanced Usage](#advanced-usage) - [Pagination](#pagination) - [Error Handling](#error-handling) - [Examples](#examples) ## Overview ### What is make_request? `make_request` is the core method of the `APIClient` class that allows you to make raw API calls to TrustCafé endpoints directly. It bypasses jobs and wrappers, giving you complete control over: - HTTP methods (GET, POST, PUT, DELETE) - API endpoints - Request paths - Request bodies - Query parameters - Authentication ### When to Use Custom Requests **Use `make_request` when:** - No wrapper or job exists for your operation - You need a custom HTTP endpoint - Building complex queries programmatically - Using specialized API features - Need maximum flexibility **Don't use when:** - Standard operation is covered by wrappers (too much boilerplate) - Simpler option exists (jobs/wrappers) - You're a beginner (wrappers/jobs are easier) ## Basic Usage ### Fundamentals ```python import trustcafeapiwrapper import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("CLIENT_ID"), client_secret=os.getenv("CLIENT_SECRET"), environment="alpha", debug=True ) API.handle_token() ``` ### The method signature ```python response = API.make_request( method="GET", endpoint="content", path="post/some-id", data=None, authenticate=True, query_params=None ) ``` ### Parameters Explained | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `method` | str | ✅ Yes | - | HTTP method: "GET", "POST", "PUT", "DELETE" | | `endpoint` | str | ✅ Yes | - | API service: "content", "auth", "audrey", etc. | | `path` | str | ✅ Yes | - | API path after endpoint | | `data` | dict | ❌ No | `None` | Request body for POST/PUT/DELETE | | `authenticate` | bool | ❌ No | `True` | Include authentication token | | `query_params` | dict | ❌ No | `None` | Query parameters for GET requests | ### Simple GET Request ```python # Get a post response = API.make_request( "GET", "content", "post/my-post-id" ) print(response) ``` ### POST Request ```python # Create a new resource response = API.make_request( "POST", "content", "post/new", data={ "postText": "My custom post", "parent": { "pk": "maintrunk#maintrunk", "sk": "maintrunk#maintrunk" } } ) print(response) ``` ### PUT Request ```python # Update an existing resource response = API.make_request( "PUT", "content", "post/my-post-id", data={ "pk": "my-post-id", "sk": "my-post-id", "newPostText": "Updated content" } ) print(response) ``` ### DELETE Request ```python # Delete a resource response = API.make_request( "DELETE", "content", "post/my-post-id" ) print(response) ``` ## Authentication ### By default, authentication is automatic: ```python # This includes the access token in the Authorization header response = API.make_request( "GET", "content", "post/my-post-id", authenticate=True # Default ) ``` ### Making guest requests (no authentication): ```python # Useful for public endpoints response = API.make_request( "GET", "content", "post/public-post", authenticate=False # No token needed ) ``` ### Manual token handling (advanced): ```python # If you need to bypass token management, set the access_token directly on the API instance API.access_token = "your-token-manually-set" response = API.make_request( "GET", "content", "post/some-path" ) ``` ## Common Patterns ### Pagination TrustCafé responses may include pagination for large result sets: ```python def get_all_posts(limit=100): """Fetch all posts with pagination using make_request directly.""" def fetch_page(last_evaluated_key=None): """Fetch a single page of posts""" response = API.make_request( "GET", "content", "post", query_params=last_evaluated_key, authenticate=True ) return response all_posts = [] last_key = None while True: page = fetch_page(last_key) items = page.get('Items', []) all_posts.extend(items) # Check if there are more pages has_more = 'LastEvaluatedKey' in page if not has_more or len(items) == 0: break # Pass the LastEvaluatedKey directly as query_params for next page last_key = page['LastEvaluatedKey'] return all_posts # Usage API.handle_token() all_posts = get_all_posts() print(f"Total posts: {len(all_posts)}") ``` ### Filtering with Query Parameters ```python # Get posts created after a specific date response = API.make_request( "GET", "content", "post/listpublic", query_params={ "creationDateAfter": "2024-01-01T00:00:00Z" }, authenticate=True ) # Get posts in a specific branch response = API.make_request( "GET", "content", "post/ref-subwiki/music", query_params={ "maxResults": 20, "showDeleted": False }, authenticate=True ) ``` ### Building Dynamic Paths ```python def get_post_by_slug(slug): """Get a post by slugified ID""" response = API.make_request( "GET", "content", f"post/id/{slug}", authenticate=True ) return response # Usage post = get_post_by_slug("my-custom-slug-123") ``` ```python def get_comments_for_post(post_id): """Get comments for a specific post""" response = API.make_request( "GET", "content", f"comment/bypostid/{post_id}", authenticate=True ) return response ``` ## Advanced Usage ### Multiple Endpoints TrustCafé has multiple API service endpoints: ```python # Content API content_response = API.make_request( "GET", "content", "post/listall", query_params={"Limit": 10} ) # Auth API (for tokens) auth_response = API.make_request( "POST", "auth", "token", data={ "client_id": "your-id", "client_secret": "your-secret", "grant_type": "client_credentials" }, authenticate=False ) # Audrey API (various services) audrey_response = API.make_request( "GET", "audrey", "some/endpoint", authenticate=True ) ``` ### Complex Payloads ```python # Building complex payloads for updates def update_post_completion(post_id, completion_status): """Update post with new completion status""" response = API.make_request( "PUT", "content", f"post/{post_id}", data={ "pk": post_id, "sk": post_id, "postText": "Updated post with completion status", "completion": completion_status }, authenticate=True ) return response # Usage update_post_completion( "current-post-pk", { "isComplete": True, "completedBy": "username", "completedAt": "2024-01-01" } ) ``` ### Batch Operations ```python def batch_create_posts(posts_data): """Create multiple posts in a single batch""" # Build batch request batch_data = { "Post": [ { "postText": post["text"], "parent": { "pk": post["parent_pk"], "sk": post["parent_sk"] } } for post in posts_data ] } response = API.make_request( "POST", "content", "post/batch-create", data=batch_data, authenticate=True ) return response ``` ### Error Handling for Custom Requests ```python def safe_api_request(method, endpoint, path, data=None): """ Safely execute an API request with proper error handling. """ try: response = API.make_request( method, endpoint, path, data=data, authenticate=True ) # Check for unexpected errors if 'error' in response: raise Exception(f"API Error: {response['error']}") return response except Exception as e: print(f"Request failed: {e}") # Handle error: retry, notify, log, etc. raise ``` ## Pagination ### Pagination Strategy When handling paginated results, always check for pagination metadata: ```python def fetch_all_content(): """Fetch all content with pagination handling""" all_content = [] next_token = None while True: params = { "Limit": 100 } if next_token: params["ExclusiveStartKey"] = next_token try: response = API.make_request( "GET", "content", "content/list", query_params=params, authenticate=True ) except Exception as e: print(f"Error fetching page: {e}") break # Process current page items = response.get('Items', []) all_content.extend(items) # Check for next page has_more = 'LastEvaluatedKey' in response if not has_more or len(items) < 100: print(f"Total items retrieved: {len(all_content)}") break next_token = response['LastEvaluatedKey'] print(f"Fetching next page (token: {next_token[:10]}...)") return all_content ``` ### Processing Large Datasets ```python def process_content_in_batches(batch_size=100): """Process content in manageable batches""" batch_number = 0 while True: batch_number += 1 print(f"Processing batch {batch_number}...") try: response = API.make_request( "GET", "content", "content/list", query_params={ "Limit": batch_size }, authenticate=True ) items = response.get('Items', []) if not items: print("No more items to process") break # Process each item in the batch for item in items: # Your processing logic here process_item(item) # Check for more items has_more = 'LastEvaluatedKey' in response if not has_more: break except Exception as e: print(f"Error in batch {batch_number}: {e}") # Retry logic could go here break print(f"Completed processing {batch_number} batches") ``` ## Error Handling ### Common Error Scenarios ```python def handle_api_request(method, endpoint, path, data=None): """Robust API request handler""" try: # Make request response = API.make_request( method, endpoint, path, data=data, authenticate=True ) # Check for API-level errors if 'error' in response: raise APIError( endpoint=endpoint, path=path, api_error=response['error'], message=response.get('message', '') ) return response except APIError as e: print(f"API Error: {e}") # Retry logic or fallback could go here raise except Exception as e: print(f"Unexpected error: {e}") # Log the error log_error(e) # Could retry or use fallback raise ``` ### Retry Logic ```python import time import logging logger = logging.getLogger(__name__) def retry_on_failure(request_func, max_retries=3, retry_delay=1): """ Retry a request function on failure. Args: request_func: Function that makes the API request max_retries: Maximum number of retry attempts retry_delay: Seconds to wait between retries Returns: The successful response """ for attempt in range(max_retries): try: return request_func() except Exception as e: logger.warning( f"Request failed (attempt {attempt + 1}/{max_retries}): {e}" ) if attempt < max_retries - 1: logger.info(f"Retrying in {retry_delay} seconds...") time.sleep(retry_delay) else: logger.error("Max retries reached. Giving up.") raise # This line should never be reached due to raise above raise Exception("Unexpected state in retry function") ``` ## Examples ### Example 1: Complex Query ```python def search_posts_advanced(search_term, branch="all", limit=50): """ Advanced search for posts with filtering. Note: This is an EXAMPLE - you'll need to adjust to match TrustCafé's actual search/query API structure. """ # Build search query query_data = { "search": { "term": search_term, "filters": { "branches": [branch] if branch != "all" else None } } } response = API.make_request( "POST", "content", "search", data=query_data, authenticate=True ) return response ``` ### Example 2: Custom Analytics Query ```python def get_post_engagement_metrics(post_id): """ Get engagement metrics for a post. Note: This requires mapping to TrustCafé's actual endpoint and data structure for engagement metrics. """ response = API.make_request( "GET", "content", f"post/{post_id}/analytics", query_params={ "metrics": [ "views", "likes", "comments", "shares" ], "timeRange": "7d" }, authenticate=True ) return response ``` ### Example 3: Multi-step Transaction ```python def create_post_and_related_content(post_text, related_items): """ Create a post and related content in a transaction-like operation. """ # Step 1: Create the main post post_response = API.make_request( "POST", "content", "post", data={ "postText": post_text, "parent": { "pk": "maintrunk#maintrunk", "sk": "maintrunk#maintrunk" } }, authenticate=True ) post_id = post_response.get('pk') # Step 2: Create related content (if applicable) if related_items: for item in related_items: API.make_request( "POST", "content", "related-item", data={ "postId": post_id, "externalId": item['id'], "type": item['type'], "url": item['url'] }, authenticate=True ) return post_response ``` ### Example 4: Conditional Operations ```python def conditional_update_post(post_id, updates, condition=None): """ Update a post only if a condition is met. Note: This maps to CousinDB/GSI-style conditional updates. Implementation depends on TrustCafé's specific API. """ update_data = { "pk": post_id, "sk": post_id, **updates } if condition: update_data["condition"] = { "expression": condition["expression"], "values": condition["values"] } response = API.make_request( "PUT", "content", f"post/{post_id}", data=update_data, authenticate=True ) return response ``` --- ## Summary `make_request` provides maximum flexibility for making API calls, but with greater complexity comes the need for careful: - **Error handling** - Always wrap in try/except - **Logging** - Track request/response for debugging - **Testing** - Verify your custom endpoints - **Documentation** - Document any custom patterns you discover **For most use cases, wrappers and jobs provide the right balance of simplicity and control. Use `make_request` when standard wrappers don't fit your needs.** --- ## Next Steps - [API Reference](API_REFERENCE.md) - Complete list of jobs - [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common operations - [Examples](../README.md#examples) - Real-world usage examples - [Troubleshooting](TROUBLESHOOTING.md) - Common issues and solutions