Update plan (Usage-Based only)

Overview

Update an existing partner-managed usage-based plan. Features are fully replaced each call (old PlanFeature rows deleted and re-inserted). Review sites are synced in place: surviving rows keep their ids/timestamps, and only sites removed from the payload are deleted.

Prerequisites

  • A bearer token. Callable with Partner tokens.
  • The planId of the record you are targeting.

Base URL

EnvironmentURL
Productionhttps://production-api.shoutaboutus.com
Developmenthttps://development-api.shoutaboutus.com

Endpoint

POST /api/v1/partner/plans/{planId}/update

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.

  • updatePartnerPlan resolves the partner via getAuthPartner (403 if none).

  • 403 unless billing_model === 'usage-based'.

  • 404 if the plan id doesn't exist.

  • 403 unless plan_type=usage-based AND is_admin_managed=0.

  • 403 unless linked to the partner (planBelongsToCompany).

  • overage_rate read-only (same as create).

Validation — identical rules as Create (same contract-model branching).

Rate limit

  • No rate limit.

Path parameters

ParameterTypeRequiredDescription
planIdintegerRequiredDigits only.

Request body

ParamInTypeRequiredDescription
planIdpathintegerYesPlan ID (route [0-9]+); a usage-based plan owned by the partner.

Body params are the same as Create plan (Usage-Based only) — including the url_finder_review_site and url_finder_landing_page feature values. 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.). On update, omitted status/enforcement_mode fall back to the plan's existing values.

{
  "plan_name": "ResponseScribe Pro",
  "price": 79.99,
  "original_price": 89.99,
  "status": "active",
  "plan_end_date": "2027-12-31",
  "short_info": "Up to 200 responses/month",
  "description": "Full AI-powered response service.",
  "enforcement_mode": "per-location",
  "features": [
    {
      "feature_name": "ai_response",
      "enabled": true,
      "hard_stop": true,
      "service_limit": 200,
      "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 updated · 200

{
  "data": {
    "data": {
      "id": 7,
      "plan_name": "ResponseScribe Basic v2",
      "price": 59.99,
      "original_price": null,
      "status": "active",
      "description": null,
      "plan_end_date": null,
      "short_info": null,
      "plan_type": "usage-based",
      "tier_slug": null,
      "enforcement_mode": "per-location",
      "is_admin_managed": false,
      "created_at": "2026-06-01T09:00:00.000000Z",
      "updated_at": "2026-06-19T11:00:00.000000Z",
      "deleted_at": null,
      "features": [
        {
          "id": 70,
          "plan_id": 7,
          "feature_name": "ai_response",
          "enabled": true,
          "service_limit": 100,
          "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": 71,
          "plan_id": 7,
          "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": 72,
          "plan_id": 7,
          "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": 31,
          "plan_id": 7,
          "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

StatusMeaning
401The bearer token is missing, expired or invalid
403No partner resolves, wrong billing model, plan-type mismatch, ownership failure, or the caller is a sub-partner
404Returned if the plan id doesn't exist
422The request failed validation — the response names the fields
500Unexpected server error

Example request

curl --request POST \
  --url "https://production-api.shoutaboutus.com/api/v1/partner/plans/123/update" \
  --header 'Authorization: Bearer <bearer-token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"plan_name": "ResponseScribe Pro", "price": 79.99, "original_price": 89.99, "status": "active", "plan_end_date": "2027-12-31", "short_info": "Up to 200 responses/month", "description": "Full AI-powered response service.", "enforcement_mode": "per-location", "features": [{"feature_name": "ai_response", "enabled": true, "hard_stop": true, "service_limit": 200, "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": []}]}'

Did this page help you?