Pricing
REST endpoints for Pricing. Base URL https://api.competlab.com; every request needs a CL-API-Key header (see Authentication). Responses wrap in { item } or { items }; errors in { error }.
Get latest pricing intelligence data
/v1/projects/{projectId}/pricingGet the latest pricing intelligence data for all competitors in a project. Returns structured pricing plans, free tier availability, billing options, enterprise pricing, promotions, and competitive gap analysis. Every tracked competitor appears — rows without pricing data carry a pricingDataAvailable reason (no pricing page found, their page didn’t respond, or a temporary problem on our side) instead of being omitted; branch on it before quoting pricing facts. A pricing page is the single most likely thing to be missing, so this is the dimension where the null contract matters most: when your own pricing couldn’t be analyzed, every metric on summary.customer is null and pricingAnalysisAvailable says why. hasFreePlan: null means we did not check, NOT that there is no free plan — a measured false is reported as false and is a real finding. The three gap flags do NOT share one null condition, so read each field’s own description rather than gating all three on the same test: all three are null when your side was unmeasured or no competitor was measured, and hasPriceGap is null on two further conditions the other two don’t carry — fewer than three comparable competitor prices, or your own price not being comparable to the market. Market figures (marketAvgPrice, pricePositionPercent) are null below that three-price threshold, which is ‘not enough market to average’, never zero. Comparable means fixed monthly amounts sharing one currency AND one licensed unit: a per-seat price is never averaged against a flat one, marketPricingUnit says which group was chosen, and every market figure must be quoted with it. A project with no completed run for this dimension yet answers 404 no_data_available — not a missing project and not a failed measurement, just nothing measured here so far; the project itself is fine and get_project still describes it. Distinguish it from project_not_found, which means the id is wrong.
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/pricing" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"item": {
"lastUpdatedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"competitorsChecked": 6,
"competitorsSuccessful": 5,
"customer": {
"domain": "yoursite.com",
"popularPlanPrice": 49,
"popularPlanCurrency": "USD",
"popularPlanUnit": "flat",
"popularPlanName": "Professional",
"pricingModel": "per-seat",
"hasFreePlan": false,
"hasFreeTrial": true,
"freeTrialDuration": "14 days",
"hasEnterprisePricing": true,
"tierCount": 3,
"pricingAnalysisAvailable": {
"available": false,
"reason": "pricing_fetch_failed"
}
},
"marketAvgPrice": 59,
"marketCurrency": "USD",
"marketPricingUnit": "flat",
"pricePositionPercent": 45,
"topPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"lowestPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"pricingSampleSize": 4,
"pricingIsReliable": true,
"competitorsWithFreePlan": 2,
"competitorsWithFreeTrial": 4,
"competitorsWithEnterprise": 3,
"activePromotionCount": 1,
"hasPriceGap": true,
"hasFreeTierGap": false,
"hasEnterpriseGap": false,
"hasPromotionAlert": true
},
"competitors": [
{
"domain": "competitor.com",
"isOwn": false,
"pricingUrl": "https://competitor.com/pricing",
"pricingLocationType": "crawled",
"content": {
"pageTitle": "Acme Pricing - Plans for Every Team",
"pageDescription": "Choose from flexible pricing plans starting at $9/month.",
"mainHeadline": "Simple, Transparent Pricing",
"pricingModel": "per-seat",
"plans": [
{
"name": "Professional",
"price": "$49/month",
"summary": "Best for growing teams with advanced features"
}
],
"popularPlanName": "Professional",
"hasFreePlan": true,
"freePlanName": "Starter",
"hasFreeTrial": true,
"freeTrialDuration": "14 days",
"billingCycleOffered": "monthly, annual",
"annualDiscountAmount": "20%",
"moneyBackGuarantee": "30-day money-back guarantee",
"hasEnterpriseCustomPricing": true,
"enterprisePlanCTA": "Contact Sales",
"pricingPageFeatures": "Unlimited projects, Priority support, SSO",
"hasActivePromotion": false,
"promotionDetails": ""
},
"pricingDataAvailable": {
"available": false,
"reason": "no_page_found"
}
}
]
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
| 404 | project_not_found · no_data_available | No project matches the id in the path. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}Get pricing run history
/v1/projects/{projectId}/pricing/historyGet paginated history of pricing intelligence monitoring runs for a project. Each entry includes run timing and summary statistics with pricing gap analysis. Fields we could not measure are null, with a sibling availability object giving the reason; never read a null as zero, false or absent, and note that a measured 0 / false / empty list is reported as itself and is a real finding.
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | number | Page number (1-indexed). Default 1. Min 1. | |
limit | number | Number of items per page. Default 20. Range 1–100. |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/pricing/history?page=1&limit=20" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"items": [
{
"runId": "507f1f77bcf86cd799439011",
"completedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"competitorsChecked": 6,
"competitorsSuccessful": 5,
"customer": {
"domain": "yoursite.com",
"popularPlanPrice": 49,
"popularPlanCurrency": "USD",
"popularPlanUnit": "flat",
"popularPlanName": "Professional",
"pricingModel": "per-seat",
"hasFreePlan": false,
"hasFreeTrial": true,
"freeTrialDuration": "14 days",
"hasEnterprisePricing": true,
"tierCount": 3,
"pricingAnalysisAvailable": {
"available": false,
"reason": "pricing_fetch_failed"
}
},
"marketAvgPrice": 59,
"marketCurrency": "USD",
"marketPricingUnit": "flat",
"pricePositionPercent": 45,
"topPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"lowestPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"pricingSampleSize": 4,
"pricingIsReliable": true,
"competitorsWithFreePlan": 2,
"competitorsWithFreeTrial": 4,
"competitorsWithEnterprise": 3,
"activePromotionCount": 1,
"hasPriceGap": true,
"hasFreeTierGap": false,
"hasEnterpriseGap": false,
"hasPromotionAlert": true
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 47,
"totalPages": 3,
"hasMore": true
}
}Paginated — pass page and limit query parameters and follow pagination.hasMore to page through the full set.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}Get pricing data for a specific run
/v1/projects/{projectId}/pricing/history/{runId}Get full pricing intelligence data for a specific historical monitoring run, including per-competitor pricing plans, billing options, free tiers, and enterprise pricing. Competitors the run could not measure appear with a pricingDataAvailable reason instead of being omitted. Fields we could not measure are null; never read a null as zero, false or absent, and note that a measured 0 / false is reported as itself and is a real finding. A run that finished but produced no summary answers 404 run_not_summarized, which is a different fact from run_not_found: the run exists, it just has nothing to report. It used to answer 200 with an all-zero summary invented from the missing one.
Path parameters
| Name | Type | Description |
|---|---|---|
runId | string | Run ID |
projectId | string | Project ID |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/pricing/history/507f1f77bcf86cd799439012" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"item": {
"runId": "507f1f77bcf86cd799439011",
"completedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"competitorsChecked": 6,
"competitorsSuccessful": 5,
"customer": {
"domain": "yoursite.com",
"popularPlanPrice": 49,
"popularPlanCurrency": "USD",
"popularPlanUnit": "flat",
"popularPlanName": "Professional",
"pricingModel": "per-seat",
"hasFreePlan": false,
"hasFreeTrial": true,
"freeTrialDuration": "14 days",
"hasEnterprisePricing": true,
"tierCount": 3,
"pricingAnalysisAvailable": {
"available": false,
"reason": "pricing_fetch_failed"
}
},
"marketAvgPrice": 59,
"marketCurrency": "USD",
"marketPricingUnit": "flat",
"pricePositionPercent": 45,
"topPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"lowestPriceCompetitor": {
"domain": "rival.com",
"price": 99,
"planName": "Enterprise"
},
"pricingSampleSize": 4,
"pricingIsReliable": true,
"competitorsWithFreePlan": 2,
"competitorsWithFreeTrial": 4,
"competitorsWithEnterprise": 3,
"activePromotionCount": 1,
"hasPriceGap": true,
"hasFreeTierGap": false,
"hasEnterpriseGap": false,
"hasPromotionAlert": true
},
"competitors": [
{
"domain": "competitor.com",
"isOwn": false,
"pricingUrl": "https://competitor.com/pricing",
"pricingLocationType": "crawled",
"content": {
"pageTitle": "Acme Pricing - Plans for Every Team",
"pageDescription": "Choose from flexible pricing plans starting at $9/month.",
"mainHeadline": "Simple, Transparent Pricing",
"pricingModel": "per-seat",
"plans": [
{
"name": "Professional",
"price": "$49/month",
"summary": "Best for growing teams with advanced features"
}
],
"popularPlanName": "Professional",
"hasFreePlan": true,
"freePlanName": "Starter",
"hasFreeTrial": true,
"freeTrialDuration": "14 days",
"billingCycleOffered": "monthly, annual",
"annualDiscountAmount": "20%",
"moneyBackGuarantee": "30-day money-back guarantee",
"hasEnterpriseCustomPricing": true,
"enterprisePlanCTA": "Contact Sales",
"pricingPageFeatures": "Unlimited projects, Priority support, SSO",
"hasActivePromotion": false,
"promotionDetails": ""
},
"pricingDataAvailable": {
"available": false,
"reason": "no_page_found"
}
}
]
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
| 404 | run_not_found · run_not_summarized | No run matches the id in the path. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}