---
title: "Add a competitor to a brand"
method: POST
path: "/api/admin/brand-competitors"
tags: ["Brand Competitors"]
---

# Add a competitor to a brand

`POST /api/admin/brand-competitors`

## Request

- Base URL: `https://api.sponsorunited.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.sponsorunited.com/api/admin/brand-competitors`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/sponsorunited/apis/sponsorunited-api/docs/api/admin/brand-competitors/post.ts> (a function with typed inputs; its response is checked against the schema)

Adds a competitor by name, optionally linked to a SponsorUnited brand. The row is marked admin-added, so a re-scrape never removes it. A linked add counts as approved, removes any unlinked row with the same name and writes the name as a synonym of the linked brand, unless that name is already a synonym of another brand (the link is still saved). A competitor an admin removed earlier is restored instead of duplicated. Requires approve-company permission.

## Request body

- StoreBrandCompetitorRequest
  - `source_brand_id` integer, required: The brand (companies.id) the competitor is added to.
  - `raw_name` string, required: Competitor name as the admin typed it.
  - `target_brand_id` integer, nullable: SponsorUnited brand (companies.id) to link the competitor to. Leave out to add an unlinked competitor. Linking writes the name as a synonym of that brand.

## Response `201`

Competitor added.

- object
  - `data` BrandCompetitorResource
    - `id` integer
    - `source_brand_id` integer
    - `raw_name` string, nullable
    - `normalized_name` string, nullable
    - `raw_name_from_target_brand` boolean: True when raw_name was copied from the target brand name because the scraped name was not kept.
    - `linked` boolean: True when the competitor is linked to a SponsorUnited brand.
    - `target_brand` object, nullable
      - `id` integer
      - `name` string
      - `logo` string, nullable
    - `decision_source` 'auto_linked' | 'approved' | 'admin_added' | 'unlinked': admin_added: added by an admin. unlinked: scraped, no SponsorUnited brand. approved: scraped and linked, confirmed by an admin. auto_linked: scraped and linked by the matcher, not yet confirmed (a rejected link is listed here too).
    - `origin` 'scraped' | 'admin'
    - `decision` 'auto_linked' | 'approved' | 'rejected', nullable
    - `match_source` 'ems' | 'meilisearch' | 'brand_synonym' | 'none', nullable
    - `linked_by_parent_brand` boolean: True when the competitor is linked to a brand that owns it rather than to its own SponsorUnited brand (for example Apple for Apple Watch): either the scrape named a parent brand for a competitor with no brand of its own, or the matched brand's name is only part of the competitor's name. No synonym is written for such a link. A later scrape that matches the competitor to its own brand replaces the link, unless an admin has confirmed the link, renamed the competitor, or removed it. An admin who links or re-points the competitor clears this flag.
    - `score_at_decision` integer, nullable
    - `decided_at` string, date-time, nullable
    - `decided_by` object, nullable
      - `id` integer
      - `name` string, nullable
      - `email` string, nullable
    - `last_seen_at` string, date-time
    - `created_at` string, date-time, nullable
    - `updated_at` string, date-time, nullable

## Other responses

- `401`: Unauthenticated
- `403`: Forbidden
- `422`: Validation error: missing brand, name without letters or digits, name longer than 255 characters once letters such as ß are spelled out, linked to itself, or already a visible competitor.

---

[API](https://skmtc.dev/sponsorunited/apis/sponsorunited-api.md) - [All operations](https://skmtc.dev/sponsorunited/apis/sponsorunited-api/llms.txt) - [OpenAPI document](https://skmtc.dev/sponsorunited/apis/sponsorunited-api/revisions/cbf1ca5bff8f?raw)
