Skip to content

Brand Management Guide

Overview

Brands represent the manufacturers or owners of products in your catalog. Every product in the Operations module can be associated with a brand, giving you a structured way to track provenance, present brand information to customers, and report on brand-level performance across your inventory.

The brand entity is intentionally lightweight: it captures identity fields (name, slug, logo, website), a description for internal or customer-facing use, and a simple active/inactive status flag. This simplicity makes brands fast to create and maintain. Bulk operations and export capabilities allow operations teams to manage large brand catalogs efficiently without requiring individual record edits.

Brand Lifecycle

Brands follow a two-state active/inactive lifecycle. Unlike entities with complex status progressions, a brand is either available for product association or it is not. This keeps catalog hygiene straightforward: deactivating a brand hides it from product creation workflows without deleting historical data or orphaning existing product records.

StatusDescriptionEffect on Products
ActiveBrand is available for useProducts can be created or updated to use this brand
InactiveBrand is suspended from useExisting product associations are preserved; new associations are blocked

Deactivation vs. Deletion

Deactivating a brand is non-destructive. Products already linked to the brand retain the association and remain fully functional. Use deactivation when a brand is temporarily unavailable or under review. Use deletion only when the brand was created in error and has no product associations.

Brand Fields

FieldTypeRequiredDescription
idULID stringAuto-generatedUnique identifier, e.g. 01hwxyz123abc456def789ghi0
namestringYesDisplay name of the brand
slugstringNoURL-safe identifier, auto-generated from name if omitted
descriptionstringNoInternal or customer-facing brand description
logo_urlstringNoAbsolute URL to the brand's logo image
website_urlstringNoBrand's public website URL
is_activebooleanNoActive status flag, defaults to true
created_atISO 8601 datetimeAuto-generatedRecord creation timestamp
updated_atISO 8601 datetimeAuto-generatedRecord last-modified timestamp

API Endpoints

All brand endpoints are scoped under /api/v1/operations/brands. Every request requires a valid Bearer token.

List Brands

Endpoint: GET /api/v1/operations/brands

Query Parameters:

ParameterTypeDescription
searchstringFilter by name or slug (partial match)
is_activebooleanFilter by active status (true or false)
has_productsbooleanFilter to brands with (true) or without (false) associated products
sort_bystringSort field: name, created_at, updated_at
sort_directionstringasc or desc (default: asc)
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 25, max: 100)

Example Request:

bash
GET /api/v1/operations/brands?is_active=true&has_products=true&sort_by=name&sort_direction=asc&per_page=50

Response (200 OK):

json
{
  "success": true,
  "message": "Brands retrieved successfully",
  "data": [
    {
      "id": "01hwxyz123abc456def789ghi0",
      "name": "Acme Industrial",
      "slug": "acme-industrial",
      "description": "Leading manufacturer of industrial tools and equipment.",
      "logo_url": "https://cdn.example.com/brands/acme-industrial-logo.png",
      "website_url": "https://www.acme-industrial.com",
      "is_active": true,
      "created_at": "2025-10-01T08:00:00Z",
      "updated_at": "2025-11-15T14:30:00Z"
    },
    {
      "id": "01hwxyz789def012ghi345jkl6",
      "name": "BlueStar Electronics",
      "slug": "bluestar-electronics",
      "description": "Consumer electronics and accessories.",
      "logo_url": "https://cdn.example.com/brands/bluestar-logo.png",
      "website_url": "https://www.bluestar-elec.com",
      "is_active": true,
      "created_at": "2025-10-12T11:20:00Z",
      "updated_at": "2025-10-12T11:20:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 50,
    "total": 38,
    "last_page": 1
  }
}

Create a Brand

Endpoint: POST /api/v1/operations/brands

Request Body:

json
{
  "name": "Acme Industrial",
  "slug": "acme-industrial",
  "description": "Leading manufacturer of industrial tools and equipment.",
  "logo_url": "https://cdn.example.com/brands/acme-industrial-logo.png",
  "website_url": "https://www.acme-industrial.com",
  "is_active": true
}

Required Fields:

  • name - Display name of the brand

Optional Fields:

  • slug - Auto-generated from name if omitted; must be unique within the tenant
  • description - Free-text description
  • logo_url - Must be a valid URL if provided
  • website_url - Must be a valid URL if provided
  • is_active - Defaults to true

Response (201 Created):

json
{
  "success": true,
  "message": "Brand created successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Acme Industrial",
    "slug": "acme-industrial",
    "description": "Leading manufacturer of industrial tools and equipment.",
    "logo_url": "https://cdn.example.com/brands/acme-industrial-logo.png",
    "website_url": "https://www.acme-industrial.com",
    "is_active": true,
    "created_at": "2025-12-17T10:30:00Z",
    "updated_at": "2025-12-17T10:30:00Z"
  },
  "meta": {}
}

Error Response (422 Unprocessable Entity):

json
{
  "success": false,
  "message": "The given data was invalid.",
  "data": null,
  "meta": {
    "errors": {
      "name": ["The name field is required."],
      "slug": ["The slug has already been taken."]
    }
  }
}

Get Brand Details

Endpoint: GET /api/v1/operations/brands/{id}

Example:

bash
GET /api/v1/operations/brands/01hwxyz123abc456def789ghi0

Response (200 OK):

json
{
  "success": true,
  "message": "Brand retrieved successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Acme Industrial",
    "slug": "acme-industrial",
    "description": "Leading manufacturer of industrial tools and equipment.",
    "logo_url": "https://cdn.example.com/brands/acme-industrial-logo.png",
    "website_url": "https://www.acme-industrial.com",
    "is_active": true,
    "created_at": "2025-10-01T08:00:00Z",
    "updated_at": "2025-11-15T14:30:00Z"
  },
  "meta": {}
}

Error Response (404 Not Found):

json
{
  "success": false,
  "message": "Brand not found.",
  "data": null,
  "meta": {}
}

Update a Brand

Endpoint: PUT /api/v1/operations/brands/{id}

All fields are optional - send only the fields you want to change.

Request Body:

json
{
  "description": "Updated: premium industrial tools for professional contractors.",
  "logo_url": "https://cdn.example.com/brands/acme-industrial-logo-v2.png",
  "website_url": "https://www.acme-industrial.com/professional",
  "is_active": true
}

Response (200 OK):

json
{
  "success": true,
  "message": "Brand updated successfully",
  "data": {
    "id": "01hwxyz123abc456def789ghi0",
    "name": "Acme Industrial",
    "slug": "acme-industrial",
    "description": "Updated: premium industrial tools for professional contractors.",
    "logo_url": "https://cdn.example.com/brands/acme-industrial-logo-v2.png",
    "website_url": "https://www.acme-industrial.com/professional",
    "is_active": true,
    "updated_at": "2025-12-17T14:00:00Z"
  },
  "meta": {}
}

Delete a Brand

Endpoint: DELETE /api/v1/operations/brands/{id}

Before Deleting

Deletion is permanent and will fail if the brand has associated products. Either reassign or delete the products first, or consider deactivating the brand instead to preserve historical data.

Response (200 OK):

json
{
  "success": true,
  "message": "Brand deleted successfully",
  "data": null,
  "meta": {}
}

Error Response (409 Conflict):

json
{
  "success": false,
  "message": "Cannot delete brand with associated products. Reassign products before deleting.",
  "data": null,
  "meta": {}
}

Bulk Operations

Bulk Activate

Endpoint: POST /api/v1/operations/brands/bulk-activate

Request Body:

json
{
  "brand_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6",
    "01hwxyzabc123def456ghi789j0"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Brands activated successfully",
  "data": {
    "processed": 3,
    "successful": 3,
    "failed": 0
  },
  "meta": {}
}

Bulk Deactivate

Endpoint: POST /api/v1/operations/brands/bulk-deactivate

Request Body:

json
{
  "brand_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Brands deactivated successfully",
  "data": {
    "processed": 2,
    "successful": 2,
    "failed": 0
  },
  "meta": {}
}

Bulk Delete

Endpoint: POST /api/v1/operations/brands/bulk-delete

Irreversible Operation

Bulk delete is permanent. Brands that still have associated products will be skipped and reported in the failed count. Review brand-product associations before issuing a bulk delete.

Request Body:

json
{
  "brand_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6"
  ]
}

Response (200 OK):

json
{
  "success": true,
  "message": "Bulk delete completed",
  "data": {
    "processed": 2,
    "successful": 1,
    "failed": 1,
    "failures": [
      {
        "id": "01hwxyz789def012ghi345jkl6",
        "reason": "Brand has associated products"
      }
    ]
  },
  "meta": {}
}

Brand Statistics

Endpoint: GET /api/v1/operations/brands/statistics

Returns aggregate counts for the brand catalog. Use this endpoint for dashboard widgets or catalog health checks.

Response (200 OK):

json
{
  "success": true,
  "message": "Brand statistics retrieved successfully",
  "data": {
    "total": 45,
    "active": 38,
    "inactive": 7,
    "with_products": 32,
    "without_products": 13
  },
  "meta": {}
}

Export Brands

Endpoint: GET /api/v1/operations/brands/export

Query Parameters:

ParameterTypeDescription
formatstringExport format: csv or json

Example Requests:

bash
GET /api/v1/operations/brands/export?format=csv
GET /api/v1/operations/brands/export?format=json

The response is the exported file content with an appropriate Content-Disposition header for browser download. The export includes all brand fields and respects the current tenant's data boundary.

Business Scenarios

Scenario 1: Onboarding a New Supplier's Brand Portfolio

Context: A new supplier agreement covers eight brands. You need to create all of them, set logos from the supplier's media kit, and immediately make them available for the product import that follows.

Workflow:

  1. Create each brand with name, logo URL, website, and description sourced from the supplier's brand guide
  2. Confirm all are active (default behavior) so they're immediately selectable during product import
  3. Run the statistics endpoint to verify the expected count of active brands

API Calls:

bash
# 1. Create brand one
POST /api/v1/operations/brands
{
  "name": "Acme Industrial",
  "description": "Industrial tools for professional contractors.",
  "logo_url": "https://cdn.example.com/brands/acme-industrial-logo.png",
  "website_url": "https://www.acme-industrial.com"
}

# 2. Repeat for remaining brands...

# 3. Verify with statistics
GET /api/v1/operations/brands/statistics
# Confirm "active" count increased by 8

Scenario 2: Seasonal Brand Suspension and Reactivation

Context: Three seasonal brands are no longer being stocked for the off-season. You want to prevent them from appearing in product creation screens without deleting any data. They will be reactivated when stock returns.

Workflow:

  1. Identify the IDs of the seasonal brands
  2. Bulk deactivate them before the off-season begins
  3. Verify using the list endpoint filtered to inactive brands
  4. At the start of the next season, bulk activate the same brands

API Calls:

bash
# 1. Deactivate for off-season
POST /api/v1/operations/brands/bulk-deactivate
{
  "brand_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6",
    "01hwxyzabc123def456ghi789j0"
  ]
}

# 2. Verify
GET /api/v1/operations/brands?is_active=false
# Confirm the three brands appear

# 3. Reactivate at season start
POST /api/v1/operations/brands/bulk-activate
{
  "brand_ids": [
    "01hwxyz123abc456def789ghi0",
    "01hwxyz789def012ghi345jkl6",
    "01hwxyzabc123def456ghi789j0"
  ]
}

Scenario 3: Catalog Audit and Cleanup

Context: A periodic catalog review reveals several brands that were created as placeholders and never had products assigned. These should be removed to keep the catalog clean.

Workflow:

  1. Pull brand statistics to quantify the scope (without_products count)
  2. List all brands filtered to has_products=false
  3. Review the list and identify which are safe to delete vs. which are simply pending product import
  4. Export the final list for record-keeping before deletion
  5. Bulk delete the confirmed placeholder brands

API Calls:

bash
# 1. Check scope
GET /api/v1/operations/brands/statistics
# Note "without_products" count

# 2. Retrieve the list
GET /api/v1/operations/brands?has_products=false&per_page=100

# 3. Export for audit trail
GET /api/v1/operations/brands/export?format=csv

# 4. Delete confirmed placeholders
POST /api/v1/operations/brands/bulk-delete
{
  "brand_ids": [
    "01hwxyzaaa111bbb222ccc333d0",
    "01hwxyzeee555fff666ggg777h0"
  ]
}

Best Practices

1. Slug Consistency

Allow the system to auto-generate slugs from the brand name unless you have a specific reason to set them manually. Auto-generated slugs are consistent, URL-safe, and unique-enforced by the system. If you override a slug, use lowercase letters, numbers, and hyphens only - no spaces or special characters. Slugs cannot be changed after products reference the brand in external integrations that use slug-based lookups.

2. Logo and Asset Management

Host brand logos on a stable CDN or object storage service before creating or updating brand records. Avoid linking to third-party URLs that may change or expire, as broken logo URLs degrade the product catalog UI. Use a consistent naming convention for asset files (e.g., {slug}-logo.png) to make bulk updates straightforward. Recommended minimum logo dimensions are 400×400 pixels in PNG format with a transparent background.

3. Maintain Active/Inactive Discipline

Reserve the is_active=false state for brands that are genuinely suspended from use. Avoid leaving brands inactive indefinitely as a form of soft-archiving - if a brand is permanently retired and has no products, delete it. If it has products, deactivate it and document the reason in an internal note or your team's wiki. Running GET /api/v1/operations/brands/statistics monthly gives you a quick signal when the inactive count is growing without purpose.

4. Pre-Import Verification

Before bulk-importing products that reference brands by name or slug, verify that all expected brands exist and are active by calling GET /api/v1/operations/brands?is_active=true&per_page=100. Compare the returned list against your import manifest. Creating missing brands before the product import avoids partial import failures and eliminates the need for post-import cleanup.

5. Treat Statistics as a Health Signal

The with_products vs. without_products split in the statistics endpoint is a direct measure of catalog health. A large without_products count typically means either brands were pre-created speculatively (clean them up) or a product import failed partway (investigate). Build a periodic check of this endpoint into your catalog maintenance routine.

Integration Points

With Products

Brands are a core attribute of the Product entity. When creating or updating a product, you reference the brand by its ULID. Deactivating a brand does not cascade to products - existing associations are preserved. If you delete a brand, all products currently associated with it must be reassigned first. Refer to the Product Management Guide for details on the brand field in product create/update requests.

With Product Variants

Product variants inherit their brand association from the parent product. You do not set a brand at the variant level. When filtering inventory or reports by brand, all variants of a branded product are implicitly included.

With Reporting and Analytics

The statistics endpoint is designed for dashboard consumption. For deeper brand-level reporting - such as revenue or stock value per brand - use the Analytics module's product-level aggregations filtered by brand ID.

With Exports and Data Pipelines

The export endpoint produces a flat representation of brand data suitable for loading into spreadsheet tools, BI platforms, or syncing back to a supplier portal. If you need scheduled exports, integrate the export endpoint into a cron-driven data pipeline using your infrastructure's scheduled job tooling.

Troubleshooting

Brand Creation Fails with Slug Conflict

Error: "The slug has already been taken."

Cause: Another brand in the same tenant already uses the auto-generated or manually supplied slug.

Solution:

  1. Search for the conflicting brand: GET /api/v1/operations/brands?search={slug}
  2. If the existing brand is the same entity (e.g., a duplicate entry), delete or update the duplicate
  3. If the brands are legitimately distinct, supply a unique slug manually (e.g., acme-industrial-tools vs. acme-industrial-supplies)

Cannot Delete Brand

Error: "Cannot delete brand with associated products."

Cause: One or more products are still linked to this brand.

Solution:

  1. Retrieve the brand's products: GET /api/v1/operations/products?brand_id={brand_id}
  2. Reassign each product to a different brand via PUT /api/v1/operations/products/{id} with an updated brand_id
  3. Alternatively, if the products should also be removed, delete the products first
  4. Retry the brand deletion once no products remain associated

Bulk Operation Reports Unexpected Failures

Symptom: A bulk activate/deactivate/delete call returns a failed count greater than zero without clear explanation.

Solution:

  1. Check the failures array in the response body - each entry includes the brand ID and the failure reason
  2. Common causes: brand ID not found (ULID typo or wrong tenant context), attempting to delete a brand with products
  3. Fix the listed issues individually, then re-run the bulk operation with only the previously-failed IDs

Documentation for SynthesQ CRM/ERP Platform