Colab Commerce
v1Stable

Colab Commerce REST API

Programmatic access to leads, lead activities, and the company–retailer relationships that power your network. JSON everywhere, API-key authenticated, policy-scoped to the calling user.

BASE URLhttps://api.colabcommerce.com/v1
Download OpenAPIGet authenticated
On this page

Introduction

The Colab Commerce v1 API is a JSON REST interface that mirrors the resources you work with in the dashboard. Every endpoint is mounted under the /v1 namespace, accepts and returns application/json, and is policy-scoped to the user whose API key made the request.

The API exposes six resources: Leads, Lead Activities, Company Retailers, Company Retailer Locations, Products, and a public Store Locator search endpoint. List endpoints are searchable and paginated; mutation endpoints return the freshly-serialized resource on success.

Authentication

The v1 API uses API key authentication. Send the key on every request via the X-Api-Key header.

The header value may optionally be prefixed with an identifier and a colon — the actual key is the substring after the last :. Both of the following are accepted:

  • X-Api-Key: abc123def456…
  • X-Api-Key: user-id:abc123def456…

API keys are minted per user (either a CompanyUser or a RetailerUser) and are generated from the application via POST /company_users/:id/generate_api_key or POST /retailer_users/:id/generate_api_key.

If the header is missing or the key cannot be matched, the API responds with 401 Unauthorized. After authentication, requests are still subject to Pundit policy checks — the role and resource of the authenticated user determine what records are visible and which attributes may be written.

Keep keys secret. Never embed an API key in client-side code, mobile apps, or public repositories. All requests should originate from a trusted server.
Authenticated request
curl https://api.colabcommerce.com/v1/leads \
  -H "X-Api-Key: $CC_API_KEY"
Missing or invalid key401 response
{
  "error": "Unauthorized"
}

Conventions

Identifiers

All resources are identified by UUIDs. Path parameters named :id accept the resource UUID.

Request bodies

Resources accept application/json bodies wrapped under the resource key, for example { "lead": { … } }.

The v1 controller automatically maps top-level keys that name a nested association (e.g. phone_numbers, emails, location, lead_activities) to their ActiveRecord _attributes form. You do not need to send phone_numbers_attributes explicitly — the API rewrites phone_numbers to phone_numbers_attributes before validation.

Status codes

StatusMeaning
200 OKSuccessful read or update.
201 CreatedResource created.
204 No ContentResource deleted.
400 Bad RequestMalformed JSON request body.
401 UnauthorizedMissing or invalid API key.
403 ForbiddenAuthenticated but not authorized by policy.
404 Not FoundResource does not exist (or is outside policy scope).
422 Unprocessable EntityValidation failure, missing root parameter, unknown STI type, unpermitted parameter, or a destroy callback halt. See Errors.

Errors

All non-2xx responses return a JSON body with a consistent shape. 4xx responses include a numeric status, a reason-phrase error, and a human-readable message. 422 Unprocessable Entity responses additionally include an errors map (for backward compatibility) and a structured details array carrying a stable, programmatic code for each error.

The API returns 422 in the following cases:

  • Validation failure on create/update — details[].code is the ActiveRecord error code (blank, taken, invalid, too_short, …).
  • Missing required root parameter (e.g. posting {} instead of { "lead": { … } }) — code: "missing".
  • Unknown STI type on a polymorphic resource — code: "invalid" on field type.
  • Unpermitted parameter when strict params is enabled — code: "unpermitted".
  • Destroy callback halt — model-specific code with message "Failed to destroy the record".

Auth failures (401) and authorization failures (403) use a smaller envelope — { "error": "Unauthorized" } — without the extended fields.

Validation failure422
{
  "status": 422,
  "error": "Unprocessable Entity",
  "message": "Validation failed: Name can't be blank, Phone numbers is invalid",
  "errors": {
    "name": ["can't be blank"],
    "phone_numbers": ["is invalid"]
  },
  "details": [
    {
      "field": "name",
      "code": "blank",
      "message": "can't be blank",
      "full_message": "Name can't be blank"
    },
    {
      "field": "phone_numbers",
      "code": "invalid",
      "message": "is invalid",
      "full_message": "Phone numbers is invalid"
    }
  ]
}
Missing root parameter422
{
  "status": 422,
  "error": "Unprocessable Entity",
  "message": "param is missing or the value is empty: lead",
  "errors": { "lead": ["is missing"] },
  "details": [
    {
      "field": "lead",
      "code": "missing",
      "message": "is missing",
      "full_message": "Lead is missing"
    }
  ]
}
Other errors
{
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid JSON: ..."
}
{ "error": "Unauthorized" }
{ "error": "Unauthorized", "status": 403 }
{
  "status": 404,
  "error": "Not Found",
  "message": "Couldn't find Lead with 'id'=00000000-0000-0000-0000-000000000000"
}

Leads

A Lead represents a sales prospect owned by a company or retailer. Leads have many phone_numbers, emails, and lead_activities, an optional location, and an assignee.

Serialized fields

FieldTypeNotes
iduuid
namestring
statusstringDefault "New".
created_atdatetimeISO 8601.
phone_numbersarrayEach: { id, phone_number, country_code, formatted }.
emailsarrayEach: { id, email }.
assigneeobjectPolymorphic (e.g. CompanyRetailerLocation).
locationobject{ id, address_name, street_line_one, street_line_two, postal_code, city, province, country, latitude, longitude }

Writable attributes

Subject to the caller's role-based policy. Typical attributes include name, owner_id, owner_type, assignee_id, assignee_type, source_id, and source_type. Nested associations are sent as plain keys and rewritten server-side:

  • emails: [{ id, email, _destroy }]
  • phone_numbers: [{ id, phone_number, country_code, _destroy }]
  • lead_activities: [{ message, type, source_id, source_type, body }]
  • location: { street_line_one, street_line_two, city, province, postal_code, country }

On create, owner is forced to the authenticated user's current company or retailer; do not rely on supplying owner_id/owner_type to override it.

GET/v1/leads

List leads

Returns a paginated list of leads visible to the authenticated user. Supports the standard search and pagination parameters.

Request
curl https://api.colabcommerce.com/v1/leads \
  -H "X-Api-Key: $CC_API_KEY"
GET/v1/leads/{id}

Retrieve a single lead

Returns a single serialized lead by UUID. Responds with 404 Not Found if the record is outside the caller's policy scope.

RequestcURL
curl https://api.colabcommerce.com/v1/leads/<id> \
  -H "X-Api-Key: $CC_API_KEY"
POST/v1/leads

Create a lead

Creates a lead owned by the authenticated user's company or retailer. Phone numbers, emails, and location are accepted inline and persisted in one request.

On duplicate detection, the new lead is merged into the existing duplicate and the merged record is returned in the response body.

Request
curl https://api.colabcommerce.com/v1/leads \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lead": {
      "name": "Jane Customer",
      "phone_numbers": [
        { "phone_number": "+15555550100", "country_code": "US" }
      ],
      "emails": [{ "email": "jane@example.com" }],
      "location": { "city": "Calgary", "province": "AB", "country": "CA" }
    }
  }'
201 CreatedResponse
{
  "id": "5f0e9c1b-…",
  "name": "Jane Customer",
  "status": "New",
  "created_at": "2026-05-18T14:22:51Z",
  "phone_numbers": [
    {
      "id": "…",
      "phone_number": "+15555550100",
      "country_code": "US",
      "formatted": "(555) 555-0100"
    }
  ],
  "emails": [{ "id": "…", "email": "jane@example.com" }],
  "location": {
    "city": "Calgary",
    "province": "AB",
    "country": "CA"
  }
}
PATCH/v1/leads/{id}

Update a lead

Updates a lead and any nested associations. Pass _destroy: true on a nested item (with its id) to remove it.

RequestcURL
curl -X PATCH https://api.colabcommerce.com/v1/leads/<id> \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lead": {
      "status": "Qualified",
      "emails": [
        { "id": "<email-id>", "_destroy": true },
        { "email": "jane.new@example.com" }
      ]
    }
  }'
DELETE/v1/leads/{id}

Delete a lead

Permanently deletes a lead. Responds with 204 No Content on success.

RequestcURL
curl -X DELETE https://api.colabcommerce.com/v1/leads/<id> \
  -H "X-Api-Key: $CC_API_KEY"

Lead activities

A LeadActivity is an event attached to a Lead (e.g. an inbound call, outbound email, note, sample request, etc.). The type field is a single-table-inheritance discriminator naming one of the following subclasses:

  • LeadActivity::InboundCall
  • LeadActivity::OutboundCall
  • LeadActivity::OutboundEmail
  • LeadActivity::Voicemail
  • LeadActivity::Note
  • LeadActivity::SampleRequest
  • LeadActivity::QuoteRequest
  • LeadActivity::Referral
  • LeadActivity::Purchase
  • LeadActivity::Assignment
  • LeadActivity::StatusChange

Authorization is delegated to the parent Lead: show?LeadPolicy#show?, create?/update? LeadPolicy#update?, destroy? LeadPolicy#destroy?.

Writable attributes

FieldType
lead_iduuid (required)
typestring
source_typestring
source_iduuid
crm_idstring
crm_providerstring
crm_data_cacheobject (jsonb)
dataobject (jsonb) — subclass-specific payload (e.g. transcript, body, message)
GET/v1/lead_activities

List lead activities

Returns a paginated, policy-scoped list of activities. Supports the standard search and pagination parameters.

GET/v1/lead_activities/{id}

Retrieve a lead activity

Returns a single activity. The embedded lead object is included in the response.

POST/v1/lead_activities

Create a lead activity

Pick the type that matches the event you're recording and put subclass-specific fields (call transcript, email body, note text, etc.) under data.

Request
curl https://api.colabcommerce.com/v1/lead_activities \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lead_activity": {
      "lead_id": "<lead-id>",
      "type": "LeadActivity::Note",
      "data": { "body": "Spoke with Jane — she\'s coming in Saturday." }
    }
  }'
PATCH/v1/lead_activities/{id}

Update a lead activity

Updates the activity record. Typically used to attach CRM metadata (crm_id, crm_provider, crm_data_cache) after syncing to an external system.

DELETE/v1/lead_activities/{id}

Delete a lead activity

Removes the activity. Responds with 204 No Content.

Company retailers

A CompanyRetailer is the join between a Company and a Retailer and represents that retailer's relationship to the company (territory, lead-routing flags, physical address, etc.).

Serialized fields

FieldTypeNotes
iduuid
namestring
sending_leadsboolean
receiving_leadsboolean
is_ccboolean
territory_iduuid
retailer_locations_countinteger
typestring
retailerobjectEmbedded retailer.
companyobjectEmbedded company.
territoryobjectEmbedded territory.
physical_addressobjectEmbedded location.

Writable attributes

company_retailer_params permits retailer_id, company_id, name, territory_id, sending_leads, receiving_leads, and a nested physical_address object (rewritten to physical_address_attributes server-side) with addressable_id, addressable_scope, address_name, street_line_one, street_line_two, postal_code, city, province, and country.

Role-based policies may further restrict which attributes a given caller can change — for example, a retailer user is typically limited to name, receiving_leads, and physical_address.

GET/v1/company_retailers

List company retailers

Returns the company retailers visible to the authenticated user, with the same search/pagination semantics as other index endpoints.

GET/v1/company_retailers/{id}

Retrieve a company retailer

Returns a single company retailer including embedded company, retailer, territory, and physical_address.

POST/v1/company_retailers

Create a company retailer

Joins a Company to a Retailer and configures their relationship. The physical_address is sent as a nested object — no _attributes suffix needed.

RequestcURL
curl https://api.colabcommerce.com/v1/company_retailers \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_retailer": {
      "company_id": "5f0e…",
      "retailer_id": "9b2a…",
      "name": "Acme — Calgary North",
      "sending_leads": true,
      "receiving_leads": true,
      "physical_address": {
        "street_line_one": "123 Main St",
        "city": "Calgary",
        "province": "AB",
        "postal_code": "T2P 1J9",
        "country": "CA"
      }
    }
  }'
PATCH/v1/company_retailers/{id}

Update a company retailer

Update lead-routing flags, territory, or address. Retailer users are typically restricted to name, receiving_leads, and physical_address.

DELETE/v1/company_retailers/{id}

Delete a company retailer

Removes the join. Responds with 204 No Content.

Company retailer locations

A CompanyRetailerLocation is the per-location join between a CompanyRetailer and a RetailerLocation. It owns location-level settings (delivery radius, business hours, the physical address, and contact email/phone) and acts as the routing target for leads.

Records are scoped by CompanyRetailerLocationPolicy: company admins see all locations for their company; territory managers see only locations within their territory; retailer admins see locations whose retailer_location belongs to their retailer.

Serialized fields

id, name (falls back to the linked retailer_location.name when blank), sending_leads, receiving_leads, delivery_radius, website, record_calls, mask_emails, place_id, timezone, company_id, company_retailer_id, retailer_location_id, territory_id, company_store_type_id, and embedded physical_address, general_email, and general_phone_number objects (which read through to the linked retailer location when present).

Writable attributes (by role)

  • Company admin — all attributes except receiving_leads.
  • Company territory manager, Company retailer manager — all except receiving_leads and territory_id.
  • Company location user receiving_leads, record_calls, mask_emails only.
  • Company sales associate — cannot create or modify.
  • Retailer admin, Retailer store manager receiving_leads only.

Nested keys physical_address, general_email, general_phone_number, delivery_areas, and retailer_location_hours are rewritten to their _attributes form server-side — clients send them as plain keys.

GET/v1/company_retailer_locations

List company retailer locations

Returns a paginated, policy-scoped list of locations. Supports the standard search and pagination parameters.

GET/v1/company_retailer_locations/{id}

Retrieve a company retailer location

Returns a single location with its embedded physical_address, general_email, and general_phone_number.

POST/v1/company_retailer_locations

Create a company retailer location

Creates a location for an existing CompanyRetailer. Pass the nested address, email, and phone alongside the main resource — the controller rewrites them to their _attributes form before validation.

RequestcURL
curl https://api.colabcommerce.com/v1/company_retailer_locations \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_retailer_location": {
      "company_id": "5f0e…",
      "company_retailer_id": "9b2a…",
      "retailer_location_id": "c7d1…",
      "name": "Acme — Calgary Downtown",
      "sending_leads": true,
      "delivery_radius": 25
    },
    "physical_address": {
      "street_line_one": "123 Main St",
      "city": "Calgary",
      "province": "AB",
      "postal_code": "T2P 1J9",
      "country": "CA"
    },
    "general_email": { "email": "store@example.com" },
    "general_phone_number": { "country_code": "1", "phone_number": "5555550100" }
  }'
PATCH/v1/company_retailer_locations/{id}

Update a company retailer location

Update routing flags, delivery settings, hours, or the contact email/phone. Attribute permissions are role-gated — see the table above.

DELETE/v1/company_retailer_locations/{id}

Delete a company retailer location

Removes the location. Responds with 204 No Content.

Products

Product represents an item in a company's catalog. Products are scoped by ProductPolicy: company users see only their company's products; retailer users see all products since they need to look up SKUs when handling leads from any brand.

Note. The v1 Products API does not expose image upload or image-management endpoints. Use the JWT-authenticated API to attach product images.

Serialized fields

id, sku (unique per company_id), name, category, url, collection_id, external_id, and company_id.

Writable attributes

sku, name, company_id, category, url, collection_id, external_id. No nested associations.

Validation

  • sku and name are required — missing values return 422 with code: "blank".
  • sku is unique within a company_id — duplicates return 422 with code: "taken".
  • company_id is required.
GET/v1/products

List products

Returns a paginated, policy-scoped list of products. Supports the standard search and pagination parameters.

GET/v1/products/{id}

Retrieve a product

Returns a single serialized product by UUID.

POST/v1/products

Create a product

Adds a product to a company's catalog. sku must be unique within company_id.

Request
curl https://api.colabcommerce.com/v1/products \
  -H "X-Api-Key: $CC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product": {
      "sku": "SOFA-001",
      "name": "Coastal Linen Sofa",
      "company_id": "5f0e…",
      "category": "Sofas",
      "url": "https://example.com/products/coastal-linen-sofa"
    }
  }'
PATCH/v1/products/{id}

Update a product

Update any of the writable attributes. Returns the updated product on success.

DELETE/v1/products/{id}

Delete a product

Removes the product. Responds with 204 No Content.

Store locator

A public geographic search over the CompanyRetailerLocation index — intended to power consumer-facing store locator widgets. Unlike every other v1 endpoint, the store locator does not require an X-Api-Key.

Identify the company whose locations should be searched via either the X-Cc-Id request header or the cc_id query parameter (the header wins when both are present). Results are filtered to locations where sending_leads and receiving_leads are both true and a physical address is present. No Pundit scope is applied.

Search modes

  • Point search — supply latitude, longitude, and radius (e.g. "50mi", "25km"). Results are ranked by _score with a Gaussian distance boost and per-store-type boosts.
  • Bounding box search — supply top_left_latitude, top_left_longitude, bottom_right_latitude, bottom_right_longitude. Results are ordered by geo-distance from the centre of the box.

If neither set of coordinates is supplied the endpoint returns 400 Bad Request with { "error": "Invalid search parameters" }. A 404 Not Found is returned when neither the header nor the query parameter resolves to an existing company.

Filters

per_page (point search only; defaults to 15), store_type, city, province, country, plus product filters: stocked_product_ids, available_product_ids, stocked_product_external_ids, available_product_external_ids, stocked_product_skus, available_product_skus, stocked_product_collection_ids, and available_product_collection_ids. Collection filters take precedence over the corresponding SKU / external_id filters.

GET/v1/store_locator/{id}

Public store locator — show a single location

Returns a single CompanyRetailerLocation using the same rich payload as the locator search results — address, email, phone number, hours, and stocked / available products.

Like the search endpoint this action is public — no X-Api-Key required and no policy scope applied, so any caller who knows the location's UUID can read it. Neither the X-Cc-Id header nor the cc_id query parameter is required.

Returns 404 Not Found when no CompanyRetailerLocation matches the supplied id.

Request
GET /v1/store_locator/9b8f0d2e-1a3c-4b6d-8e9f-0a1b2c3d4e5f
Response200 OK
{
  "company_retailer_location": {
    "id": "9b8f0d2e-1a3c-4b6d-8e9f-0a1b2c3d4e5f",
    "name": "Calgary Downtown",
    "full_name": "Acme — Calgary Downtown",
    "retailer_name": "Acme",
    "store_type": "Showroom",
    "website": "https://acme.example.com/calgary",
    "place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "address": {
      "id": "a1b2c3d4-1111-2222-3333-444455556666",
      "address_name": "Acme Calgary Downtown Showroom",
      "street_line_one": "123 Main St",
      "street_line_two": "Suite 200",
      "postal_code": "T2P 1J9",
      "city": "Calgary",
      "province": "AB",
      "country": "CA",
      "latitude": 51.0447,
      "longitude": -114.0719,
      "string": "123 Main St, Suite 200, Calgary, AB T2P 1J9, Canada"
    },
    "email": {
      "id": "e1111111-2222-3333-4444-555566667777",
      "email": "store@example.com"
    },
    "phone_number": {
      "id": "p1111111-2222-3333-4444-555566667777",
      "phone_number": "5555550100",
      "country_code": "1",
      "formatted": "+1 (555) 555-0100"
    },
    "retailer_location_hours": [
      {
        "day": "monday",
        "open": true,
        "open_at_hour": 9,
        "open_at_minute": 0,
        "close_at_hour": 18,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-20T15:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "tuesday",
        "open": true,
        "open_at_hour": 9,
        "open_at_minute": 0,
        "close_at_hour": 18,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-21T15:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "wednesday",
        "open": true,
        "open_at_hour": 9,
        "open_at_minute": 0,
        "close_at_hour": 18,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-22T15:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "thursday",
        "open": true,
        "open_at_hour": 9,
        "open_at_minute": 0,
        "close_at_hour": 20,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-23T15:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "friday",
        "open": true,
        "open_at_hour": 9,
        "open_at_minute": 0,
        "close_at_hour": 20,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-24T15:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "saturday",
        "open": true,
        "open_at_hour": 10,
        "open_at_minute": 0,
        "close_at_hour": 17,
        "close_at_minute": 0,
        "utc_offset_minute": -360,
        "open_at": "2026-05-25T16:00:00Z",
        "timezone": "America/Edmonton"
      },
      {
        "day": "sunday",
        "open": false,
        "open_at_hour": null,
        "open_at_minute": null,
        "close_at_hour": null,
        "close_at_minute": null,
        "utc_offset_minute": -360,
        "open_at": null,
        "timezone": "America/Edmonton"
      }
    ],
    "retailer_location_products": [
      {
        "product_id": "11111111-aaaa-bbbb-cccc-222222222222",
        "sku": "WS-3001-CHAR",
        "name": "Westwood Sofa",
        "category": "Sofas",
        "url": "https://example.com/products/westwood-sofa",
        "external_id": "SOFA-WS-3001",
        "collection_id": "33333333-aaaa-bbbb-cccc-444444444444",
        "stocked": true,
        "available": true,
        "images": [
          {
            "id": "i1111111-2222-3333-4444-555566667777",
            "url": "https://cdn.example.com/products/ws-3001/hero.jpg",
            "thumbnail_url": "https://cdn.example.com/products/ws-3001/thumb.jpg"
          }
        ]
      },
      {
        "product_id": "55555555-aaaa-bbbb-cccc-666666666666",
        "sku": "WL-3002-CHAR",
        "name": "Westwood Loveseat",
        "category": "Sofas",
        "url": "https://example.com/products/westwood-loveseat",
        "external_id": "SOFA-WL-3002",
        "collection_id": "33333333-aaaa-bbbb-cccc-444444444444",
        "stocked": false,
        "available": true,
        "images": []
      }
    ]
  }
}

OpenAPI spec

A machine-readable OpenAPI 3.1 description of the v1 API is available for use in your tooling (codegen, mock servers, Postman, etc.).

Changelog

  • v1.2 — Added the public Store Locator show endpoint (GET /v1/store_locator/:id) for fetching a single CompanyRetailerLocation by id. Like the search endpoint it requires no X-Api-Key.
  • v1.1 — Added Company Retailer Locations, Products, and the public Store Locator search endpoint. Expanded the error envelope to include status, error, message, and a structured details array (with a stable code) alongside the existing errors map.
  • v1.0 — Initial release. Endpoints for /leads, /lead_activities, and /company_retailers; API-key authentication via X-Api-Key; Searchkick-powered list endpoints with q, opts, and include parameters.

Need a hand getting started?

Tell us what you're building and we'll help you mint an API key and wire it up.