Create plan (Usage-Based only)
Overview
Create a new usage-based plan owned by the authenticated partner and link it via company_plans. The endpoint stores features + review sites from the payload; plan_type is forced usage-based, is_admin_managed=0.
Prerequisites
- A bearer token. Callable with Partner tokens.
Base URL
| Environment | URL |
|---|---|
| Production | https://production-api.shoutaboutus.com |
| Development | https://development-api.shoutaboutus.com |
Endpoint
POST /api/v1/partner/plans/create
Authentication
-
Requires a bearer token in the
Authorization: Bearer <bearer-token>header. -
Who can call it: Partner tokens.
-
Usage-based only. Sits in the authenticated group, with no extra route-level guard.
-
createPartnerPlanresolves the partner viagetAuthPartner(403 if none). It returns 403 (Partner plans can only be created for usage-based companies.) unlesspartner.billing_model === 'usage-based'. -
overage_rateis never accepted — it is server-derived from the partner billing config. -
The caller must be a main-partner user. Sub-partner users are always rejected with 403 (
Sub-partners cannot create, edit, or delete plans — plans are managed by your main partner.).
Validation branches on the partner contract_model. For model-a, hard_stop is required per enabled feature, and service_limit is required only when hard_stop=true (else unlimited + overage). For model-b, hard_stop is forced true at persist (ignored here) and service_limit is optional. per_pull_data is exempt (quota derives from review-site cadence). ≥1 feature must be enabled.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
plan_name | string | Yes | max 100 |
price | number | Yes | min 0 |
original_price | number | No | nullable, min 0 |
status | string | No | active|inactive (default active) |
plan_end_date | date | No | nullable |
short_info | string | No | nullable, max 255 |
description | string | No | nullable |
enforcement_mode | string | No | per-location|aggregate-pool (default per-location) |
features | array | Yes | min 1, ≥1 enabled |
features.*.feature_name | string | Yes | ai_response, review_flag, response_to_past_reviews, response_posting, review_widgets, review_solicitation_email, review_solicitation_sms, review_qr_code, insight_report, competitive_analysis, per_pull_data, url_finder_review_site, url_finder_landing_page |
features.*.enabled | boolean | No | default true |
features.*.hard_stop | boolean | model-a: Yes (per enabled, non-per_pull_data) | model-b: ignored |
features.*.service_limit | integer | conditional | model-a: required when hard_stop=true; min 1 |
features.*.limit_type | string | conditional | monthly|quantity; required if response_to_past_reviews |
review_sites | array | Yes | min 1 |
review_sites.*.review_site_id | integer | Yes | exists in review_sites |
review_sites.*.pull_frequency | string | Yes | hourly|daily|weekly|monthly |
review_sites.*.pull_interval | integer | No | 1–30 |
review_sites.*.pull_times | array | No | each HH:MM |
review_sites.*.pull_days | array | No | nullable; each int 0–6 |
Rate limit
- No rate limit.
Request body
{
"plan_name": "ResponseScribe Basic",
"price": 49.99,
"original_price": 59.99,
"status": "active",
"plan_end_date": "2027-12-31",
"short_info": "Up to 50 responses/month",
"description": "Full AI-powered response service.",
"enforcement_mode": "per-location",
"features": [
{
"feature_name": "ai_response",
"enabled": true,
"hard_stop": true,
"service_limit": 50,
"limit_type": null
},
{
"feature_name": "response_to_past_reviews",
"enabled": true,
"hard_stop": false,
"limit_type": "monthly"
},
{
"feature_name": "per_pull_data",
"enabled": true
}
],
"review_sites": [
{
"review_site_id": 1,
"pull_frequency": "daily",
"pull_interval": 1,
"pull_times": [
"06:00"
],
"pull_days": []
}
]
}Response
200 — Plan created · 200
200{
"data": {
"data": {
"id": 11,
"plan_name": "ResponseScribe Basic",
"price": 49.99,
"original_price": 59.99,
"status": "active",
"description": "Full AI-powered response service.",
"plan_end_date": "2027-12-31T00:00:00.000000Z",
"short_info": "Up to 50 AI responses/month",
"plan_type": "usage-based",
"tier_slug": null,
"enforcement_mode": "per-location",
"is_admin_managed": false,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"features": [
{
"id": 88,
"plan_id": 11,
"feature_name": "ai_response",
"enabled": true,
"service_limit": 50,
"limit_type": null,
"included_units": null,
"overage_rate": null,
"hard_stop": true,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"partner_billing_enabled": true
},
{
"id": 89,
"plan_id": 11,
"feature_name": "response_to_past_reviews",
"enabled": true,
"service_limit": null,
"limit_type": "monthly",
"included_units": 10,
"overage_rate": "0.0500",
"hard_stop": false,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"partner_billing_enabled": true
},
{
"id": 90,
"plan_id": 11,
"feature_name": "response_posting",
"enabled": true,
"service_limit": null,
"limit_type": null,
"included_units": null,
"overage_rate": "0.0400",
"hard_stop": false,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"partner_billing_enabled": true
},
{
"id": 91,
"plan_id": 11,
"feature_name": "review_widgets",
"enabled": true,
"service_limit": 5,
"limit_type": null,
"included_units": null,
"overage_rate": null,
"hard_stop": true,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"partner_billing_enabled": true
},
{
"id": 92,
"plan_id": 11,
"feature_name": "per_pull_data",
"enabled": true,
"service_limit": null,
"limit_type": null,
"included_units": null,
"overage_rate": null,
"hard_stop": false,
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"partner_billing_enabled": true
}
],
"plan_review_sites": [
{
"id": 30,
"plan_id": 11,
"review_site_id": 44,
"pull_frequency": "daily",
"pull_interval": 1,
"pull_times": [
"06:00"
],
"pull_days": [],
"created_at": "2026-06-19T11:00:00.000000Z",
"updated_at": "2026-06-19T11:00:00.000000Z",
"deleted_at": null,
"review_site": {
"id": 44,
"name": "Google",
"link": "http://maps.google.com",
"review_site_button": "img-google.png",
"review_site_color": "#3774ee",
"max_rate": 5,
"status": 0,
"show": true,
"use_sau_scrapper": "Y",
"publisher": "maps.google.com",
"auth_type": "oauth",
"scraping_enabled": true,
"posting_enabled": true,
"self_form_enabled": "N",
"email_on_reply": "N",
"deleted_at": null,
"created_at": "2017-01-09T11:33:00.000000Z",
"updated_at": "2020-05-04T02:44:57.000000Z"
}
}
]
}
}
}Errors
| Status | Meaning |
|---|---|
401 | The bearer token is missing, expired or invalid |
403 | No partner resolves, the partner is not usage-based, or the caller is a sub-partner |
422 | The request failed validation — the response names the fields |
500 | Unexpected server error |
Example request
curl --request POST \
--url "https://production-api.shoutaboutus.com/api/v1/partner/plans/create" \
--header 'Authorization: Bearer <bearer-token>' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"plan_name": "ResponseScribe Basic", "price": 49.99, "original_price": 59.99, "status": "active", "plan_end_date": "2027-12-31", "short_info": "Up to 50 responses/month", "description": "Full AI-powered response service.", "enforcement_mode": "per-location", "features": [{"feature_name": "ai_response", "enabled": true, "hard_stop": true, "service_limit": 50, "limit_type": null}, {"feature_name": "response_to_past_reviews", "enabled": true, "hard_stop": false, "limit_type": "monthly"}, {"feature_name": "per_pull_data", "enabled": true}], "review_sites": [{"review_site_id": 1, "pull_frequency": "daily", "pull_interval": 1, "pull_times": ["06:00"], "pull_days": []}]}'Updated 10 days ago
