Skip to main content

Endpoint

POST /api/sdk/companies/identify
Upsert a company record and optionally link it to an end user. Called automatically when you use identifyCompany().

Authentication

Requires your publishable widget key in the Authorization header:
Authorization: Bearer ab_live_xxxxxxxxxxxxxxxx

Request Body

company_id
string
required
Your system’s unique identifier for this company.
user_id
string
If provided, the user with this external ID is linked to the company.
traits
object
Company attributes. Recognized fields (name, domain, industry, plan, employee_count, signed_up_at, renewal_date, renewal_status, contract_term, payment_terms, on_contract, mrr, arr) are stored in dedicated columns. All other fields are stored in custom_fields.Do not send id, external_id, org_id, created_at, updated_at, health_score, team_size, or last_contacted_at. These are managed by Halo and the endpoint returns HTTP 400 if any of them appear in the body. Use signed_up_at to record the date the company onboarded in your product. See System-managed fields.
context
object
Structured context entries for AI consumption. Stored in the context jsonb column. Merged with existing context (new keys overwrite).
user_token
string
JWT signed with your Identity Secret (HS256). Required when identity verification is enabled for your workspace and a user_id is provided. The token’s user_id claim must match the user_id field. See Identity Verification.

Example

curl -X POST https://api.haloagents.ai/api/sdk/companies/identify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ab_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "company_id": "company_456",
    "user_id": "user_123",
    "traits": {
      "name": "Acme Corp",
      "plan": "enterprise",
      "industry": "SaaS",
      "employee_count": 150,
      "signed_up_at": "2024-08-12T15:32:00Z",
      "space_id": "space_789"
    },
    "context": {
      "integrations": {
        "label": "Connected Integrations",
        "type": "integration",
        "value": {
          "stripe": { "connected": true, "status": "active" },
          "hubspot": { "connected": false }
        }
      }
    }
  }'
Response:
{
  "company": {
    "id": "uuid",
    "org_id": "your-org-id",
    "external_id": "company_456",
    "name": "Acme Corp",
    "domain": null,
    "industry": "SaaS",
    "plan": "enterprise",
    "employee_count": 150,
    "health_score": 0,
    "custom_fields": { "name": "Acme Corp", "plan": "enterprise", "industry": "SaaS", "employee_count": 150, "space_id": "space_789" },
    "context": { "integrations": { "label": "Connected Integrations", "type": "integration", "value": { "stripe": { "connected": true, "status": "active" }, "hubspot": { "connected": false } } } },
    "signed_up_at": "2024-08-12T15:32:00.000000+00:00",
    "created_at": "2026-01-01T00:00:00.492417+00:00",
    "updated_at": "2026-02-10T00:00:00.492417+00:00"
  }
}
signed_up_at, created_at, and updated_at are returned as Postgres timestamptz values serialized as ISO 8601 with microsecond precision and a +00:00 offset (the format shown above, e.g. 2026-04-11T12:25:19.492417+00:00, not the ...Z shorthand). created_at and updated_at are read-only Halo-managed timestamps and must not be echoed back into the traits body on subsequent calls. signed_up_at is customer-set and can be updated by sending the new value in traits on a follow-up identify call.

Rejected Traits

These keys trigger HTTP 400 if present in the traits body. See System-managed fields for context.
KeyWhy
idHalo’s internal primary key. Pass your identifier as company_id instead.
external_idHalo’s mirror of the company_id you sent. Use company_id at the top level.
org_idSet by Halo from the API key.
created_atHalo sets this when the row is inserted. Pass signed_up_at if you want to record customer signup.
updated_atMaintained automatically by upsert_company and a database trigger.
health_scoreComputed by the health scoring engine. See Health Scores.
team_sizeComputed at chat time from linked end users.
last_contacted_atSet when your team sends a broadcast, series, reply, or chat to any user at the company.

Behavior

  • If a company with the same company_id exists within your organization, it is updated
  • If it doesn’t exist, it is created
  • If user_id is provided and the user exists, the user is linked to the company
  • Context is merged: existing DB context keys are preserved, new keys are added, matching keys are overwritten
  • The combined size of traits and context must not exceed 50,000 bytes
  • signed_up_at on update uses COALESCE semantics: a new value overwrites, but null is ignored so an integration with a more authoritative date is never wiped.
  • Sending Halo-managed fields (id, external_id, org_id, created_at, updated_at, health_score, team_size, last_contacted_at) in traits returns HTTP 400 with a reserved_keys array naming every offending key, so the caller can fix them all in one round trip.