# 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*