Skip to main content
The Configuration API lets you manage Scrunch brands at scale.
It is most commonly used by:
  • Agencies onboarding dozens or hundreds of clients
  • Enterprise teams automating persona and keyword setup
  • Internal tools (brand creation, batch updates, auditing)

What you can configure

Brands

  • Name, alternative names
  • Website + alternative websites
  • Competitors
  • Personas
  • Key topics

Prompts

  • Text
  • Stage
  • Tags
  • Personas
  • Platforms

Sites (read-only)

  • List a brand’s sites and their IDs, with whether each serves Agent Pages and Agent Traffic. Other endpoints take the ID as {site_id}. Any API key with access to the brand works; no scope is required.

Example: Create a brand


Case-sensitive name matching

By default, Scrunch matches brand and competitor names against AI responses case-insensitively, so "spirit airlines", "Spirit Airlines", and "SPIRIT AIRLINES" all count as mentions. For names that are common words, acronyms, or stylized in a specific case (for example, NeXT or LUSH), enable case-sensitive matching to avoid false positives. Set case_sensitive: true on a brand or competitor to require exact-case matches for name and alternative_names. The flag defaults to false and is available on:
  • POST /brands — on the brand body and on each entry in competitors[]
  • PATCH /brands/{brand_id} — on the brand and on each competitor in the replacement list
  • POST /brands/{brand_id}/competitors and PUT /brands/{brand_id}/competitors/{competitor_id}
  • GET responses for brands and competitors return the current value
Changing case_sensitive on an existing brand triggers a re-evaluation of historical responses against the new matching rule.

Domain uniqueness across brand and competitors

Each domain can only be classified one way within a brand — either as the brand’s own or as one specific competitor’s. This keeps citation owner classification (brand / competitor / other) deterministic. Scrunch normalizes every configured URL by stripping the scheme, www., query, fragment, and trailing slash, and lowercasing it. The brand’s primary website is compared by domain only, while alternative_websites and competitor websites keep their path — so example.com/a and example.com/b on two different owners do not conflict. If a write would assign the same normalized domain to two different owners (for example, the brand’s website and a competitor’s websites, or two competitors), the request is rejected with HTTP 409 and a detail describing where the domain is already used. The check applies to:
  • POST /brands and PATCH /brands/{brand_id} (brand website, alternative_websites, and the replacement competitors[])
  • POST /brands/{brand_id}/competitors and PUT /brands/{brand_id}/competitors/{competitor_id}
Only newly introduced conflicts are blocked, so unrelated edits to a brand that already contains a legacy collision still save. To move a domain between owners, remove it from the current owner in the same request that adds it to the new one.

Asynchronous website validation

After a write commits, Scrunch checks each new or changed website URL in the background. The checks confirm that the hostname resolves to a public address and that the page responds over HTTP. These checks are advisory. They never delay, reject, or roll back a structurally valid save, so your API response is unaffected. Background checks run for URLs submitted through:
  • POST /brands and PATCH /brands/{brand_id} (brand website, alternative_websites, and competitor websites in the replacement list)
  • POST /brands/{brand_id}/competitors and PUT /brands/{brand_id}/competitors/{competitor_id} (websites)
Only new or changed URLs are checked. Malformed URLs are still rejected synchronously at write time, before the background checks run. If any check fails, Scrunch creates one aggregated notification in the dashboard notification bell for the user who owns the API key. Other users of the brand are not notified. Clicking the notification opens Configure → Brand Entities with a banner listing each affected URL and its result, such as page not found or host did not resolve. If a check could not be completed, the notification says so explicitly. An incomplete check does not mean the URL is invalid. Successful checks are silent. To clear a warning, correct the URL with another write; the updated URL is checked again. Responses that only block automated access (401, 403, or 429) do not create a warning. Sites often return these codes to automated clients while remaining reachable in a browser, so they do not indicate that the URL is invalid. If a run contains both blocked-access results and other failures, the notification reports only the other failures.
URL-validation warnings are independent of brand-performance notification preferences and cannot be disabled by those settings.

Alternative names limit

Each brand and competitor accepts up to 100 entries in alternative_names. Submitting more returns a 422 Unprocessable Entity with the message:
The cap is enforced on every write endpoint that accepts alternative_names, including:
  • POST /v1/brands and PATCH /v1/brands/{brand_id} (brand body and each competitor)
  • POST /v1/brands/{brand_id}/competitors and PUT /v1/brands/{brand_id}/competitors/{competitor_id}
If you previously sent more than 100 aliases, the collection layer silently kept only the 100 shortest. Trim your list before retrying so you control which aliases are retained.

Excluded names

Use excluded_names to list phrases that should never count as a mention of a brand or competitor, even when the phrase contains a configured name as a word. This lets a brand named Caliber match Caliber Collision while ignoring unrelated entities like Caliber Car Wash or Caliber Fitness. Scrunch blanks every occurrence of each excluded phrase that matches under the same rules as brand names (see Names in Japanese, Chinese, Korean, and Thai text) before searching the response for this entity’s names. Position and other span-based metrics are unaffected because masked spans are replaced with equal-length whitespace. Historical responses are re-evaluated when excluded_names changes, so metrics update without a manual backfill. excluded_names is a list of strings and is available on:
  • POST /v1/brands — on the brand body and on each entry in competitors[]
  • PATCH /v1/brands/{brand_id} — on the brand and on each competitor in the replacement list
  • POST /v1/brands/{brand_id}/competitors and PUT /v1/brands/{brand_id}/competitors/{competitor_id}
  • GET responses for brands and competitors return the current value
Each brand and competitor accepts up to 100 entries in excluded_names. Submitting more returns a 422 Unprocessable Entity with:
Scrunch rejects an exclusion that equals the entity’s own name or any of its alternative_names, since either would remove every mention of the entity. Remove the alternative name instead of excluding it. The check runs after Scrunch normalizes punctuation, so Caliber. and Caliber are treated as the same phrase.

Names in Japanese, Chinese, Korean, and Thai text

Scrunch normally counts a name only when it stands as a separate word, so Spirit does not match inside Spirited. Japanese, Chinese, and Thai do not put spaces between words, and Korean attaches particles directly to names. Scrunch adjusts the word-boundary rule for these scripts so mentions are still counted:
  • Names in these scripts match even when surrounding text runs directly into them, so 第一生命 matches in 第一生命の保険.
  • Korean names must still start at a word boundary but can be followed directly by a particle, so 삼성 matches in 삼성은 but 기아 does not match inside 기압.
  • Latin names next to these scripts match when the adjacent character is from one of these scripts, so Lenovo matches in LenovoノートPC. Latin names in Latin text match exactly as before.
  • One-character names still require a word boundary on both sides.
Japanese voicing marks are significant, so ダブ does not match タブレット. Accents on Latin letters are still ignored, so Café matches cafe. Because names in these scripts match without a boundary, short names can match inside longer, unrelated words. For example, モス matches inside モスクワ (Moscow). Add the longer words to excluded_names to stop them counting as mentions. The same rules apply to name, alternative_names, excluded_names, and the prompt branded flag.

Excluded websites

Use excluded_websites to list sites whose citations should not count as owned by the brand or any of its competitors. This is useful when a subdomain or path under a configured website belongs to someone else. For example, a brand with website set to example.com can exclude advisors.example.com when that subdomain is a separate business. Scrunch reclassifies excluded citations as third party (other). It does not remove them, so total citation counts stay the same and only the owner split changes. The most specific match wins, so an excluded subdomain or path overrides its parent domain. The change applies to all historical citations without re-evaluating stored responses. Exclusions are brand-wide. An entry set on a competitor applies to the whole brand, not only to that competitor. Exclusions on an archived competitor stop applying. excluded_websites is a list of strings and is available on:
  • POST /v1/brands: on the brand body and on each entry in competitors[]
  • PATCH /v1/brands/{brand_id}: on the brand and on each competitor in the replacement list
  • POST /v1/brands/{brand_id}/competitors and PUT /v1/brands/{brand_id}/competitors/{competitor_id}
  • GET responses for brands and competitors, which return the stored value
On PATCH /v1/brands/{brand_id} competitor entries and on PUT /v1/brands/{brand_id}/competitors/{competitor_id}, omitting excluded_websites leaves the stored list unchanged. Send [] to clear it.

Normalization and limits

Scrunch normalizes each entry before storing it. It strips the scheme, www., port, query, fragment, and trailing slash, then lowercases the result. A locale-only path collapses to the domain, so example.com/en-us is stored as example.com. For example, HTTPS://WWW.Partners.Example.com/ is stored as partners.example.com. GET responses return the normalized values.
  • Duplicate entries are removed.
  • Each brand and competitor accepts up to 100 entries. Submitting more returns a 422 Unprocessable Entity with excluded_websites cannot exceed 100 entries (got N); remove some before saving.
  • Entries with credentials (@) or wildcards (*) return 422. An entry already covers its subdomains, so wildcards are not needed.

Conflicts

Scrunch rejects a write with HTTP 409 when an exclusion collides with an owned website. The detail explains what would break. A write is rejected when an exclusion matches:
  • The brand’s website or alternative_websites
  • Any competitor’s websites, including competitors other than the one the exclusion is set on
  • A sub-brand website, a subdomain or path under a sub-brand website, or a parent of a sub-brand website
Adding a website that is already excluded is also rejected until you remove the exclusion.
Excluding a subdomain or path under a brand or competitor website is allowed.

Example: Add a prompt

Tag name limit

Each entry in a prompt’s tags array is limited to 64 characters. Submitting a longer tag name to POST /{brand_id}/prompts returns a 400 Bad Request with a structured detail object:
The check runs before any tag is created, so a request with one over-long name creates no tags at all. Over-long names usually come from joining several tags into a single string. Send each tag as its own array entry instead:
Tag names at exactly 64 characters are accepted. The limit applies only when creating tags; referencing existing tags is unaffected.

Duplicate prompt text

POST /{brand_id}/prompts checks the new text against the brand’s existing prompts. The outcome depends on the existing prompt’s status:
  • Active duplicate — returns 400 Bad Request with the message Prompt already exists.
  • Paused duplicate — returns 400 Bad Request with a structured detail object identifying the paused prompt, so you can resume it instead of creating a copy:
  • Archived duplicate — reactivates the archived prompt instead of returning an error.

Listing prompts

GET /{brand_id}/prompts returns active prompts by default. Pass the status query parameter to include paused or archived prompts: Any other value returns a 422 Unprocessable Entity rather than an empty list.
Each prompt in the response includes:
  • branded — true if the prompt mentions the brand
  • favorite — true if marked as a favorite in the dashboard
  • status — active, paused, or archived
For paused prompts, platforms and variants reflect the prompt’s active variants. Pausing never changes them, so they show what a resume would collect again. GET /{brand_id}/prompts/{prompt_id} returns archived prompts as well, so check the status field if you only want active ones.

Pausing and resuming prompts

PATCH /{brand_id}/prompts/{prompt_id} moves a prompt between active and paused. It requires the configure scope, and status is the only field the body accepts.
Pausing stops collection and releases the prompt’s variants from the organization’s prompt pool. The prompt keeps its configuration and all of its collected history, and stays readable through GET /{brand_id}/prompts?status=paused. Resuming restores the exact deployment the prompt had when it was paused. Scrunch re-checks the prompt pool first, so a resume can return 429 with a quota_exceeded error when the organization no longer has room for the prompt’s variants. Both transitions are idempotent: a prompt already in the requested status returns 200 with the prompt, unchanged. archived is not accepted as a target status; archive a prompt with DELETE /{brand_id}/prompts/{prompt_id} instead. A transition the prompt’s current state does not allow returns 409 with a detail.type naming the reason: A 503 with detail.type of pause_failed or resume_failed means the transition did not complete. Retry the request.

Archiving a persona pauses its prompts

Archiving a persona pauses its active custom prompts instead of archiving them. Collection and billing stop, but the prompts’ collected history stays visible on the Responses and Query APIs. The paused prompts appear under status=paused in the prompt listing. This applies to every path that archives a persona:
  • DELETE /brands/{brand_id}/personas/{persona_id}
  • PATCH /brands/{brand_id} when the personas replace-list omits an existing persona
Prompts that reference the persona only through variants keep collecting on their remaining active variants; a prompt left with no active variant is paused. Already paused, archived, and industry prompts are not affected. To resume the paused prompts, restore the persona or reassign the prompts to another persona, then resume them.

Reusing an archived persona name

When you PATCH /brands/{brand_id} with personas, the list represents the full desired state: existing personas not included are archived. If you later submit a persona without an id and the name matches a previously archived persona on the same brand, Scrunch reactivates the archived record instead of failing on the unique-name constraint. Its status returns to active and the description is replaced with the value you provide.
If Budget Traveler was previously archived on this brand, the call reactivates that persona and updates its description. To update an existing active persona instead, include its id.

Personas can’t name the brand

Scrunch sends a persona with every prompt it collects. If a persona’s name or description mentions the brand, it turns non-branded prompts into branded ones and inflates visibility. Scrunch refuses persona text that names the brand on every endpoint that writes personas:
  • POST /brands (the personas array)
  • PATCH /brands/{brand_id} (the personas replace-list)
  • POST /brands/{brand_id}/personas
  • PUT /brands/{brand_id}/personas/{persona_id}
The check uses the same rules as mention matching. It looks for the brand’s name and alternative_names, respects case_sensitive, and ignores phrases listed in excluded_names. Only text you’re writing is checked. A PATCH that resends an existing persona unchanged, or a PUT that keeps the stored name or description, isn’t refused for that text. A refused request returns 400 Bad Request with the error code PERSONA_NAMES_BRAND. Nothing is saved. On POST /brands, the brand isn’t created.
To recover, remove the matched names from the listed personas’ fields and resend the request. If the brand name is an everyday word that appears in persona text without referring to the brand, enable case_sensitive or add the phrase to the brand’s excluded_names.

Persona limits when creating a brand

POST /brands accepts an optional personas array. Each plan caps how many personas you can attach to a single brand. If the array is longer than your plan allows, the request is rejected before the brand is created — no partial brand is saved. When the limit is exceeded, the API returns 429 Too Many Requests with a quota_exceeded error:
To recover, trim the personas array to limit entries or fewer and retry the request. To find your plan’s limit, inspect the limit field in the error response or contact your account team.
Personas can also be added or replaced after a brand is created via POST /brands/{brand_id}/personas and PATCH /brands/{brand_id}. The cap is currently enforced only at brand creation.

Notes

To update prompt text, delete the old prompt and create a new one. Only tags and platforms can be updated in-place.

See all configuration endpoints

Explore the API reference →