# API Settings Integrate Powerlily with your existing tools and workflows using the Powerlily REST API. Access quotes, leads, and system data programmatically for CRM syncing, custom reporting, and automation. ## Overview The Powerlily API provides secure, authenticated access to your company's data. Use the API to: - **Sync to CRM** - Automatically push quotes and leads to Salesforce, HubSpot, or custom systems - **Build Integrations** - Create custom workflows and automation - **Extract Data** - Pull data for reporting and analytics - **Monitor Changes** - Track quote updates and lead conversions **API Version:** v1 **Protocol:** REST **Format:** JSON **Authentication:** API Key via header --- ## Getting Started ### 1. Generate Your API Key API keys are managed from the API Settings page: 1. Go to **Company Settings** > **API Settings** 2. Click **Generate New API Key** 3. Copy and store your key securely 4. The key is 64 characters and shown only once **Important:** - Keep your API key secure and never share it publicly - Store keys in environment variables or secrets managers - Regenerate your key immediately if compromised - Never expose keys in client-side code or public repositories ### 2. Base URL All API requests use this base URL: ``` https://yourcompany.powerlily.io/api/v1 ``` Or if using a custom domain: ``` https://portal.yourcompany.com/api/v1 ``` Replace `yourcompany` with your actual Powerlily subdomain or custom domain. ### 3. Authentication Every API request requires authentication via the `X-Api-Key` header: **Request Header:** ``` X-Api-Key: your_64_character_api_key_here ``` **Example Request:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/quotes" ``` **Failed Authentication:** - Missing or invalid API key returns `401 Unauthorized` - Error response: `{"error": "Invalid API key."}` --- ## Rate Limits API requests are rate limited to ensure fair usage and system stability: **Limits:** - **100 requests per minute** per API key - **60 requests per minute** per IP address (fallback) **When Exceeded:** - Status code: `429 Too Many Requests` - Wait 60 seconds before retrying - Implement exponential backoff in your integration **Best Practices:** - Cache responses when possible - Batch operations instead of making individual requests - Use pagination for large datasets - Monitor your usage to stay within limits --- ## Pagination List endpoints return paginated results. Use query parameters to navigate through pages. ### Query Parameters | Parameter | Type | Default | Description | |---|---|---|---| | `page` | integer | 1 | Page number to retrieve | | `per_page` | integer | 25 | Items per page (max: 100) | ### Pagination Metadata Every paginated response includes a `meta` object: ```json { "data": [...], "meta": { "total_count": 42, "page": 1, "per_page": 25, "total_pages": 2 } } ``` ### Example **Request page 2 with 50 items per page:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/quotes?page=2&per_page=50" ``` --- ## API Endpoints ### Quotes #### GET /api/v1/quotes Returns a paginated list of all quotes with summary information. **Use Case:** Sync quote overviews to your CRM or dashboard **Request:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/quotes" ``` **Response:** ```json { "data": [ { "id": "14bed4b2-0bf6-41ac-8f86-77eb330fe63a", "name": "John Smith", "email": "[email protected]", "address": "123 Solar Lane, Halifax, NS", "phone": "902-555-1234", "project_status": "quote", "signed": false, "sent": true, "net_cost": 25000.00, "gross_cost": 28750.00, "total_cost": 23000.00, "total_hardware_cost": 18000.00, "installation_cost": 5000.00, "total_system_capacity_kw": 8.4, "total_ac_annual_kwh": 9850.50, "monthly_bill": 175.00, "stage_name": "Proposal Sent", "workflow_name": "Sales Pipeline", "lead_id": "abc-123", "created_at": "2025-01-15T10:00:00-04:00", "updated_at": "2025-01-20T14:30:00-04:00" } ], "meta": { "total_count": 42, "page": 1, "per_page": 25, "total_pages": 2 } } ``` **Quote Fields:** - `id` - Unique quote UUID - `name` - Customer name - `email` - Customer email - `address` - Installation address - `phone` - Customer phone number - `project_status` - Status: `quote`, `won`, `in_progress`, `completed`, `lost`, `on_hold`, `cancelled` - `signed` - Boolean indicating if quote is signed - `sent` - Boolean indicating if quote has been sent to customer - `net_cost` - Total cost after incentives - `gross_cost` - Total cost including taxes - `total_cost` - Total cost before incentives - `total_hardware_cost` - Cost of all equipment - `installation_cost` - Labor and installation cost - `total_system_capacity_kw` - DC system capacity in kilowatts - `total_ac_annual_kwh` - Estimated year 1 AC production - `monthly_bill` - Customer's current monthly electricity bill - `stage_name` - Current workflow stage - `workflow_name` - Name of the workflow/pipeline - `lead_id` - Associated lead ID (if created from lead) - `created_at` - Quote creation timestamp (ISO 8601) - `updated_at` - Last modified timestamp (ISO 8601) --- #### GET /api/v1/quotes/:id Returns detailed information for a single quote, including PV arrays, panels, inverters, and equipment. **Use Case:** Get complete system design details including equipment specs and production data **Request:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/quotes/14bed4b2-0bf6-41ac-8f86-77eb330fe63a" ``` **Response:** ```json { "data": { "id": "14bed4b2-0bf6-41ac-8f86-77eb330fe63a", "name": "John Smith", "email": "[email protected]", "address": "123 Solar Lane, Halifax, NS", "phone": "902-555-1234", "project_status": "quote", "signed": false, "sent": true, "net_cost": 25000.00, "gross_cost": 28750.00, "total_cost": 23000.00, "total_hardware_cost": 18000.00, "installation_cost": 5000.00, "total_system_capacity_kw": 8.4, "total_ac_annual_kwh": 9850.50, "monthly_bill": 175.00, "latitude": 44.6488, "longitude": -63.5752, "pricing_model": "bom", "price_per_watt": null, "times_opened": 5, "stage_name": "Proposal Sent", "workflow_name": "Sales Pipeline", "lead_id": "abc-123", "created_at": "2025-01-15T10:00:00-04:00", "updated_at": "2025-01-20T14:30:00-04:00", "pv_arrays": [ { "id": 1, "number_of_panels": 20, "system_capacity_kw": 8.4, "ac_annual_kwh": 9850.50, "ac_capacity_kw": 7.6, "azimuth": 180, "tilt": 25, "losses": 14.0, "latitude": "44.6488", "longitude": "-63.5752", "monthly_production": { "jan": 520.5, "feb": 610.2, "mar": 850.3, "apr": 920.1, "may": 1050.8, "jun": 1100.4, "jul": 1080.2, "aug": 980.5, "sep": 820.3, "oct": 650.4, "nov": 480.2, "dec": 420.1 } } ], "panels": [ { "id": 1, "quantity": 20, "value": 6000.00, "per_unit_value": 300.00, "panel": { "id": "panel-uuid", "name": "Canadian Solar 420W", "model": "CS6R-420MS", "watts": 420, "efficiency": 21.5, "width": 1.048, "height": 2.108, "deg_rate": 0.995, "power_warranty": 25, "product_warranty": 12 } } ], "inverters": [ { "id": 1, "quantity": 1, "value": 2500.00, "per_unit_value": 2500.00, "inverter": { "id": "inverter-uuid", "name": "SolarEdge SE7600H", "model": "SE7600H-US", "watts": 7600, "micro": false, "panels_serviced": 25, "efficiency": 99.0, "warranty": 12 } } ], "equipment": [ { "id": 1, "quantity": 1, "value": 8500.00, "per_unit_value": 8500.00, "equipment": { "id": "equipment-uuid", "name": "Tesla Powerwall 3", "description": "Home battery storage", "battery": true, "total_capacity_kwh": 13.5, "continuous_power_rating": 11.5, "peak_power_rating": 22.0, "depth_of_discharge": 100.0, "efficiency": 90.0, "warranty": 10 } } ] } } ``` **Additional Fields in Detail View:** - `latitude`, `longitude` - Geographic coordinates - `pricing_model` - Pricing model used (e.g., `bom`, `ppw`) - `price_per_watt` - Price per watt if using PPW pricing model - `times_opened` - Number of times customer viewed the quote - `pv_arrays` - Array of solar array configurations with production data - `panels` - Array of panel line items with specifications - `inverters` - Array of inverter line items with specifications - `equipment` - Array of additional equipment (batteries, etc.) **PV Array Fields:** - `number_of_panels` - Total panels in this array - `system_capacity_kw` - DC capacity of array - `ac_annual_kwh` - Annual AC production for this array - `ac_capacity_kw` - AC capacity - `azimuth` - Compass direction (0-360°, 180 = south) - `tilt` - Roof pitch in degrees - `losses` - System losses percentage - `monthly_production` - Month-by-month production in kWh --- ### Leads #### GET /api/v1/leads Returns a paginated list of all leads captured through your lead forms. **Use Case:** Sync leads to your CRM or trigger follow-up workflows **Request:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/leads" ``` **Response:** ```json { "data": [ { "id": "lead-uuid-123", "name": "Jane Doe", "email": "[email protected]", "address": "456 Sunshine Ave, Halifax, NS", "phone": "902-555-5678", "power_bill": 200.00, "completed": false, "latitude": 44.6521, "longitude": -63.5812, "quote_id": null, "created_at": "2025-01-18T09:15:00-04:00", "updated_at": "2025-01-18T09:15:00-04:00" } ], "meta": { "total_count": 15, "page": 1, "per_page": 25, "total_pages": 1 } } ``` **Lead Fields:** - `id` - Unique lead UUID - `name` - Lead name - `email` - Lead email address - `address` - Property address - `phone` - Lead phone number - `power_bill` - Monthly power bill amount - `completed` - Boolean indicating if lead has been processed - `latitude`, `longitude` - Geographic coordinates - `quote_id` - Associated quote UUID (if converted to quote) - `created_at` - Lead capture timestamp - `updated_at` - Last modified timestamp --- #### GET /api/v1/leads/:id Returns details for a specific lead. If the lead has been converted to a quote, the `quote_id` field will contain the associated quote UUID. **Use Case:** Get lead details and check conversion status **Request:** ```bash curl -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/leads/lead-uuid-123" ``` **Response:** Same structure as individual lead object in list response, with all fields populated. --- ## Error Responses The API uses standard HTTP status codes to indicate success or failure. ### Status Codes | Status | Description | Example Response | |---|---|---| | **200** | Success | Response body contains requested data | | **401** | Unauthorized | `{"error": "Invalid API key."}` | | **404** | Not Found | `{"error": "Resource not found."}` | | **429** | Rate Limited | Too many requests, retry after cooldown | | **500** | Server Error | Internal error, contact support if persistent | ### Error Response Format ```json { "error": "Description of what went wrong" } ``` ### Common Errors **Invalid API Key:** ```json { "error": "Invalid API key." } ``` **Resource Not Found:** ```json { "error": "Resource not found." } ``` **Rate Limit Exceeded:** - Status: `429` - Wait 60 seconds before retrying - Implement exponential backoff --- ## Code Examples ### Python ```python import requests headers = {"X-Api-Key": "YOUR_KEY"} response = requests.get( "https://yourcompany.powerlily.io/api/v1/quotes", headers=headers ) quotes = response.json()["data"] for quote in quotes: print(f"Quote: {quote['name']} - ${quote['total_cost']}") ``` ### JavaScript (Node.js) ```javascript const response = await fetch( "https://yourcompany.powerlily.io/api/v1/quotes", { headers: { "X-Api-Key": "YOUR_KEY" } } ); const { data, meta } = await response.json(); data.forEach(quote => { console.log(`Quote: ${quote.name} - ${quote.total_cost}`); }); ``` ### PHP ```php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://yourcompany.powerlily.io/api/v1/quotes"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "X-Api-Key: YOUR_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $data = json_decode($response, true); foreach ($data['data'] as $quote) { echo "Quote: {$quote['name']} - \${$quote['total_cost']}\n"; } ``` ### Ruby ```ruby require 'net/http' require 'json' uri = URI('https://yourcompany.powerlily.io/api/v1/quotes') request = Net::HTTP::Get.new(uri) request['X-Api-Key'] = 'YOUR_KEY' response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end data = JSON.parse(response.body) data['data'].each do |quote| puts "Quote: #{quote['name']} - $#{quote['total_cost']}" end ``` --- ## Security Best Practices ### API Key Security **DO:** - ✅ Store keys in environment variables or secrets managers - ✅ Regenerate your key immediately if compromised - ✅ Use HTTPS only - all production requests must be encrypted - ✅ Rotate keys periodically (every 90 days recommended) - ✅ Use different keys for development and production - ✅ Consider IP whitelisting on your server for additional security **DON'T:** - ❌ Never expose your API key in client-side code - ❌ Never commit keys to version control or public repositories - ❌ Never share keys via email, chat, or other insecure channels - ❌ Never log API keys in application logs - ❌ Never use the same key across multiple environments ### Request Security - Always use HTTPS (not HTTP) - Implement rate limit handling in your code - Validate and sanitize API responses - Use secure connections (verify SSL certificates) - Implement proper error handling ### Example: Environment Variables **Python (.env file):** ``` POWERLILY_API_KEY=your_64_character_api_key_here POWERLILY_BASE_URL=https://yourcompany.powerlily.io/api/v1 ``` **Usage:** ```python import os from dotenv import load_dotenv load_dotenv() api_key = os.getenv('POWERLILY_API_KEY') base_url = os.getenv('POWERLILY_BASE_URL') ``` --- ## Common Integration Patterns ### Syncing to CRM Poll for new or updated quotes and push to your CRM: ```python import requests from datetime import datetime, timedelta # Get quotes updated in last hour one_hour_ago = datetime.now() - timedelta(hours=1) response = requests.get( f"{base_url}/quotes", headers={"X-Api-Key": api_key} ) quotes = response.json()["data"] recent_quotes = [ q for q in quotes if datetime.fromisoformat(q['updated_at']) > one_hour_ago ] # Push to CRM for quote in recent_quotes: push_to_crm(quote) ``` ### Lead Notification Webhook Alternative Since webhooks aren't available, poll for new leads: ```python import time last_check = datetime.now() while True: response = requests.get( f"{base_url}/leads", headers={"X-Api-Key": api_key} ) leads = response.json()["data"] new_leads = [ l for l in leads if datetime.fromisoformat(l['created_at']) > last_check ] for lead in new_leads: send_notification(lead) last_check = datetime.now() time.sleep(300) # Check every 5 minutes ``` ### Batch Processing Process multiple pages efficiently: ```python def get_all_quotes(): all_quotes = [] page = 1 while True: response = requests.get( f"{base_url}/quotes", headers={"X-Api-Key": api_key}, params={"page": page, "per_page": 100} ) data = response.json() all_quotes.extend(data["data"]) if page >= data["meta"]["total_pages"]: break page += 1 return all_quotes ``` --- ## Troubleshooting ### Common Issues **401 Unauthorized** - Verify API key is correct - Check that key hasn't been regenerated - Ensure `X-Api-Key` header is present - Confirm no extra spaces in header value **404 Not Found** - Verify the resource ID is correct - Check that the resource exists in your account - Ensure correct base URL (subdomain or custom domain) **429 Rate Limit** - Implement exponential backoff - Reduce request frequency - Cache responses when possible - Consider upgrading if you need higher limits **Empty Results** - Check pagination parameters - Verify data exists in your account - Try without filters first ### Testing Your Integration **Test with cURL:** ```bash curl -v -H "X-Api-Key: YOUR_KEY" \ "https://yourcompany.powerlily.io/api/v1/quotes" ``` **Check Response:** - Status code should be 200 - Content-Type should be `application/json` - Response should contain `data` and `meta` keys --- ## Support Need help with the API? **Resources:** - Review this documentation - Check code examples above - Test with cURL or Postman first **Contact:** - Email: [email protected] - Include "API Support" in subject line - Provide request/response details - Include error messages --- ## Related Documentation - [[settings/Theme Settings|Theme Settings]] - Customize your branding - [[settings/Domain Settings|Domain Settings]] - Configure your domain - [[CRM/Workflows|Workflows]] - Understand workflow stages - [[Leads/Overview|Leads Management]] - Learn about lead capture --- *Last updated: December 2025*