As part of the PantheraHive "API Integration Builder" workflow, this deliverable outlines the comprehensive specification and design for integrating with an external API to perform the create_project operation. This output provides the blueprint for developing or configuring an integration component that can programmatically create new projects within a designated Project Management System (PMS) or similar platform.
This document details the technical specifications and best practices for building an API integration specifically designed to create new projects within an external system. The primary objective is to enable automated project creation, reducing manual effort and ensuring consistency across project initiation processes. This integration will leverage the target system's API to submit project details and receive confirmation of successful creation.
The integration will target a dedicated API endpoint for project creation. This typically involves a POST request to a resource collection representing projects.
POST/api/v1/projects (or similar, depending on the target API's versioning and resource naming conventions)The request body for creating a project will be a JSON object containing all necessary project attributes. The specific fields will vary based on the target Project Management System's API schema, but common attributes are outlined below.
Content-Type: application/json
Example Request Body:
**Key Request Body Fields & Descriptions:**
* `name` (string, **required**): The unique or primary identifier for the project.
* `description` (string, optional): A detailed description of the project's purpose and scope.
* `status` (string, optional): The initial status of the project (e.g., "Planned", "Pending Approval", "Draft").
* `startDate` (string, ISO 8601 datetime, optional): The planned start date and time of the project.
* `endDate` (string, ISO 8601 datetime, optional): The planned end date and time of the project.
* `priority` (string, optional): The priority level (e.g., "High", "Medium", "Low").
* `ownerId` (string, optional): The ID of the user designated as the project owner/manager.
* `teamIds` (array of strings, optional): An array of IDs for teams assigned to the project.
* `budget` (object, optional): Financial details related to the project.
* `currency` (string): The currency code (e.g., "USD", "EUR").
* `amount` (number): The budget amount.
* `customFields` (object, optional): A flexible object to accommodate any custom fields defined in the target PMS.
### 4. Successful Response Specification (Example)
Upon successful project creation, the API will typically return an `HTTP 201 Created` status code along with a JSON response body containing the newly created project's details, including its unique identifier.
**HTTP Status Code:** `201 Created`
**Content-Type:** `application/json`
**Example Success Response Body:**
collab → generate_code - API Integration Code GenerationThis document provides a comprehensive, detailed, and professional output for the "API Integration Builder" workflow, specifically focusing on generating production-ready code. This output serves as a foundational deliverable to help you seamlessly integrate with external APIs.
The "API Integration Builder" is designed to streamline the process of connecting your applications with external services via their APIs. This initial step focuses on generating robust, well-structured, and easily customizable code that handles common API interaction patterns, including:
The generated code is provided in Python, a widely used language for backend development and API integrations, leveraging the popular requests library for HTTP communication.
Before diving into the code, understanding these core concepts will help you effectively utilize and customize the generated solution:
https://api.example.com/v1)./products, /users).* GET: Retrieve data.
* POST: Create new data.
* PUT: Update existing data (replaces the entire resource).
* PATCH: Partially update existing data.
* DELETE: Remove data.
? (e.g., ?limit=10&offset=0) to filter or paginate data.* API Keys: A secret token sent in headers or as a query parameter.
* OAuth 2.0: A more complex protocol involving token exchange (access tokens, refresh tokens) for delegated authorization.
200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error).Below is a Python ApiClient class designed for reusability, robustness, and ease of use. It includes common patterns for API interaction, authentication, error handling, and retries.
We'll provide two files:
api_client.py: Contains the core ApiClient class.main.py: Demonstrates how to use the ApiClient class.api_client.py - Core API ClientThis file defines a flexible ApiClient class that can be configured for various external APIs.
# api_client.py
import requests
import os
import logging
import time
from typing import Dict, Any, Optional, Union, Tuple
# --- Configuration for Logging ---
# Configure basic logging for better visibility into API calls and errors
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# --- Custom Exception Class for API Errors ---
class APIError(Exception):
"""Custom exception for API-related errors."""
def __init__(self, message: str, status_code: Optional[int] = None, details: Optional[Any] = None):
super().__init__(message)
self.status_code = status_code
self.details = details
def __str__(self):
detail_str = f" Details: {self.details}" if self.details else ""
return f"API Error: {self.args[0]} (Status: {self.status_code}){detail_str}"
class ApiClient:
"""
A robust and reusable client for interacting with external REST APIs.
Features:
- Configurable base URL, authentication (API Key, OAuth Token).
- Supports GET, POST, PUT, DELETE HTTP methods.
- Automatic JSON handling for request bodies and responses.
- Configurable timeouts for requests.
- Exponential backoff retry mechanism for transient errors (5xx, 429).
- Centralized error handling with custom exceptions.
- Basic logging for API interactions.
"""
def __init__(
self,
base_url: str,
api_key: Optional[str] = None,
auth_token: Optional[str] = None, # For OAuth2 Bearer tokens
timeout: int = 30,
max_retries: int = 3,
backoff_factor: float = 0.5,
default_headers: Optional[Dict[str, str]] = None
):
"""
Initializes the ApiClient.
Args:
base_url (str): The base URL of the API (e.g., "https://api.example.com/v1").
api_key (str, optional): An API key for authentication. If provided, sent as 'X-API-Key' header.
auth_token (str, optional): An OAuth2 Bearer token. If provided, sent as 'Authorization: Bearer <token>'.
Takes precedence over api_key if both are provided.
timeout (int): Default timeout for requests in seconds.
max_retries (int): Maximum number of retries for transient errors.
backoff_factor (float): Factor for exponential backoff between retries (delay = backoff_factor * (2 ** (retry_attempt - 1))).
default_headers (Dict[str, str], optional): Additional default headers to include in all requests.
"""
if not base_url:
raise ValueError("Base URL cannot be empty.")
self.base_url = base_url.rstrip('/') # Ensure no trailing slash to prevent double slashes
self.api_key = api_key
self.auth_token = auth_token
self.timeout = timeout
self.max_retries = max_retries
self.backoff_factor = backoff_factor
self.default_headers = default_headers if default_headers is not None else {}
logger.info(f"ApiClient initialized for base_url: {self.base_url}")
def _get_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
"""
Constructs the request headers, including authentication and default headers.
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
**self.default_headers
}
if self.auth_token:
headers["Authorization"] = f"Bearer {self.auth_token}"
elif self.api_key:
headers["X-API-Key"] = self.api_key # Common header for API Keys
if custom_headers:
headers.update(custom_headers)
return headers
@staticmethod
def _handle_response(response: requests.Response) -> Any:
"""
Handles the API response, checking for errors and parsing JSON.
Raises APIError for non-2xx status codes.
"""
try:
response_json = response.json()
except requests.exceptions.JSONDecodeError:
response_json = {"message": response.text} # Fallback if response is not JSON
if 200 <= response.status_code < 300:
return response_json
else:
error_message = f"API request failed with status {response.status_code}"
logger.error(f"{error_message}: {response_json}")
raise APIError(
message=error_message,
status_code=response.status_code,
details=response_json
)
def _make_request(
self,
method: str,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
json: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
stream: bool = False,
retries: int = 0
) -> Any:
"""
Internal method to make an HTTP request with retry logic.
"""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
request_headers = self._get_headers(headers)
# 'json' parameter is preferred for sending JSON bodies with requests library
# 'data' parameter is for form-encoded data or raw bytes
if data and json:
logger.warning("Both 'data' and 'json' parameters provided. 'json' will be used.")
data = None # Clear data if json is provided
try:
logger.debug(f"Making {method} request to {url} with params: {params}, json: {json}, headers: {request_headers}")
response = requests.request(
method,
url,
params=params,
json=json, # Use json parameter for JSON body
data=data, # Use data parameter for other body types (e.g., form-encoded)
headers=request_headers,
timeout=self.timeout,
stream=stream
)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
logger.info(f"Successfully received response from {url} (Status: {response.status_code})")
return self._handle_response(response)
except requests.exceptions.HTTPError as e:
# Handle specific status codes for retries (e.g., 5xx, 429 Too Many Requests)
if e.response is not None and e.response.status_code in [429, 500, 502, 503, 504]:
if retries < self.max_retries:
wait_time = self.backoff_factor * (2 ** retries)
logger.warning(
f"Request to {url} failed with status {e.response.status_code}. "
f"Retrying in {wait_time:.2f} seconds... (Attempt {retries + 1}/{self.max_retries})"
)
time.sleep(wait_time)
return self._make_request(method, endpoint, params, data, json, headers, stream, retries + 1)
else:
logger.error(f"Max retries reached for {url}.")
# If not a retryable error or max retries reached, raise APIError
logger.error(f"HTTP Error for {url}: {e}")
raise self._handle_response(e.response) # Use _handle_response to parse error body
except requests.exceptions.Timeout as e:
logger.error(f"Request to {url} timed out after {self.timeout} seconds: {e}")
raise APIError(f"Request timed out for {url}", details=str(e))
except requests.exceptions.ConnectionError as e:
logger.error(f"Connection error to {url}: {e}")
raise APIError(f"Network connection error to {url}", details=str(e))
except requests.exceptions.RequestException as e:
logger.error(f"An unexpected request error occurred for {url}: {e}")
raise APIError(f"An unexpected error occurred during request to {url}", details=str(e))
def get(
self,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
stream: bool = False
) -> Any:
"""Performs a GET request."""
return self._make_request("GET", endpoint, params=params, headers=headers, stream=stream)
def post(
self,
endpoint: str,
data: Optional[Dict[str, Any]] = None,
json: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None
) -> Any:
"""Performs a POST request."""
return self._make_request("POST", endpoint, data=data, json=json, headers=headers)
def put(
self
Key Response Body Fields & Descriptions:
id (string): The unique identifier assigned to the new project by the external system. This is crucial for future operations (e.g., updating, retrieving).createdAt (string, ISO 8601 datetime): Timestamp indicating when the project was created.updatedAt (string, ISO 8601 datetime): Timestamp indicating the last update time (initially same as createdAt).Robust error handling is critical for any integration. The integration should be designed to gracefully manage various error scenarios reported by the target API.
* 400 Bad Request: Indicates invalid input data (e.g., missing required fields, incorrect data types, validation failures). The response body should provide specific error messages.
* Example: {"code": "INVALID_INPUT", "message": "Project name is required."}
* 401 Unauthorized: Authentication credentials are missing or invalid.
* Example: {"code": "UNAUTHORIZED", "message": "Invalid API key or token."}
* 403 Forbidden: The authenticated user/application does not have permission to create projects.
* Example: {"code": "FORBIDDEN", "message": "User lacks 'project:create' permission."}
* 409 Conflict: A resource with the same unique identifier (e.g., project name if it must be unique) already exists. This is important for idempotency.
* Example: {"code": "DUPLICATE_RESOURCE", "message": "Project 'PantheraHive Q3 Product Launch' already exists."}
* 429 Too Many Requests: Rate limiting has been exceeded. The integration should implement retry mechanisms with exponential backoff.
* 500 Internal Server Error: An unexpected error occurred on the target API's side. The integration should log this and potentially retry.
The integration must adhere to best practices for secure API communication.
* OAuth 2.0 (Recommended): If the target API supports OAuth 2.0 (e.g., Client Credentials Grant for server-to-server integrations), this is the preferred method. The integration will need to manage token acquisition and refresh.
* API Key: If OAuth 2.0 is not available, a secure API key (sent via Authorization header or query parameter) can be used. API keys must be securely stored (e.g., in environment variables, secret management services) and never hardcoded.
The high-level workflow for creating a project via this integration is as follows:
POST request to the /api/v1/projects endpoint.* Success (201 Created): Parse the response to extract the new project's ID and other details. Update the source system with this information (e.g., link the internal record to the external project ID).
* Error (4xx/5xx): Log the error, handle specific error codes (e.g., retry on 429, report validation errors), and notify relevant stakeholders.
To prevent duplicate project creation if a request is retried, consider the target API's idempotency support.
Idempotency-Key header, the integration should generate a unique key for each project creation attempt and include it in the request. This ensures that even if the request is sent multiple times, the project is created only once.To proceed with the implementation of this API integration, the following steps are recommended: