Skip to main content

Overview

This tutorial walks through the process of creating Company records via API. We’ll assume the starting point is a CSV of company data to upload, but in general the approach here can be used for any data source. The Company object is one of the standard objects provided by default in Unify. Along with Person, it powers many of the core capabilities in Unify. The unique identifier of a company is its website domain.

Prerequisites

Before you begin, be sure to generate an API key if you don’t already have one. You’ll pass it as an HTTP header on each request:
X-Api-Key: <YOUR_API_KEY>

Steps

1

Prepare a CSV of companies

Construct a CSV file that includes the companies you want to send into Unify. The columns don’t need to match the attribute names in Unify since we’ll map them over in the script.Here’s an example CSV for the sake of this tutorial:
example_companies.csv
Website,Company Name,LinkedIn
"tailscale.com","Tailscale","linkedin.com/company/tailscale"
"dagster.io","Dagster","linkedin.com/company/dagsterlabs"
"hashicorp.com","HashiCorp","linkedin.com/company/hashicorp"
"langchain.com","LangChain","linkedin.com/company/langchain"
"vercel.com","Vercel","linkedin.com/company/vercel"
"datadoghq.com","Datadog","linkedin.com/company/datadog"
"temporal.io","Temporal Technologies","linkedin.com/company/temporal-technologies"
"elastic.co","Elastic","linkedin.com/company/elastic-co"
"clickhouse.com","ClickHouse","linkedin.com/company/clickhouseinc"
"streamnative.io","StreamNative","linkedin.com/company/streamnative"
The only required column is a website or domain for the company. That’s because Unify uses a company’s domain as its unique identifier. Everything else is optional.
2

Install dependencies

You’ll just need Python 3.10+ and the requests library.
pip install requests
3

Understand the request payload

We’ll use the upsert method to create companies if they don’t exist and update them if they do. The upsert method is the recommended way of sending data into Unify since it gives you the most control and never fails if a record already exists.The upsert requests we send will have two properties:
  • match — This contains the value(s) used to match existing records. In this case, the only value we will use for matching is the domain.
  • defaults — This contains the values used when creating or updating the record.
The defaults property never overwrites values if the record already exists, which is a nice property. For full control over updating existing records, you can specify create and update properties instead, but we’ll skip that for now.
Here’s what a single request looks like for one company row:
PUT /data/v1/objects/company/records
Host: api.unifygtm.com
Content-Type: application/json
X-Api-Key: <YOUR_API_KEY>

{
  "match": {
    "domain": "unifygtm.com"
  },
  "defaults": {
    "name": "Unify",
    "domain": "unifygtm.com",
    "description": "A nice company with a top-notch API.",
    "industry": "Software",
    "linkedin_url": "https://www.linkedin.com/company/unifygtm"
  }
}
4

Assemble the script

The script needs to perform a few steps:
  1. Read the CSV
  2. Construct an upsert request payload for each row
  3. Send the requests
Here’s a complete script that does just that:
import csv
import os
import sys
from typing import Optional, TypedDict

from requests import HTTPError, Session

API_BASE = "https://api.unifygtm.com/data/v1"
API_KEY_ENV = "UNIFY_API_KEY"


class CompanyData(TypedDict):
    name: Optional[str]
    domain: str
    linkedin_url: Optional[str]
    lead_source: Optional[str]


def read_csv(path: str) -> list[CompanyData]:
    rows: list[CompanyData] = []

    with open(path, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for i, raw in enumerate(reader, start=1):
            domain = raw.get("Website")
            if not domain:
                print(f"[WARN] Row {i} skipped: missing required 'domain'")
                continue

            rows.append(
                CompanyData(
                    name=raw.get("Company Name"),
                    domain=domain,
                    linkedin_url=raw.get("LinkedIn"),
                    lead_source="CSV Tutorial",
                )
            )

    return rows


def upsert_company(client: Session, row: CompanyData) -> dict:
    response = client.put(
        url=f"{API_BASE}/objects/company/records",
        json={
            "match": {"domain": row["domain"]},
            "defaults": row,
        },
    )
    response.raise_for_status()
    return response.json()


def main():
    if len(sys.argv) != 2:
        print("Usage: python upload_companies.py <path/to/companies.csv>")
        return 2

    api_key = os.getenv(API_KEY_ENV)
    if not api_key:
        print(f"Environment variable {API_KEY_ENV} is required")
        return 2

    csv_path = sys.argv[1]
    companies = read_csv(csv_path)
    if not companies:
        print("No valid rows to process.")
        return 0

    client = Session()
    client.headers.update({"x-api-key": api_key})

    success = 0
    for row in companies:
        try:
            data = upsert_company(client, row)
            record_id = data.get("data", {}).get("id", "unknown")
            print(f"[OK] Upserted company {row['domain']} -> id={record_id}")
            success += 1
        except HTTPError as e:
            print(f"[ERROR] {row['domain']}: {e.response.json()}")
        except Exception as e:
            print(f"[ERROR] {row['domain']}: {e}")

    print(f"Done. Upserted {success} of {len(companies)} companies.")


if __name__ == "__main__":
    main()
5

Run the script

Set your API key as an environment variable and then run the script:
bash
export UNIFY_API_KEY="<YOUR_API_KEY>"
python send_companies.py example_companies.csv
You should see output like this:
Output
[OK] Upserted company tailscale.com -> id=04caa07b-cbdd-4bb4-9de4-49abdc106aaa
[OK] Upserted company dagster.io -> id=18cd4c36-3e6c-4778-88c3-4bd5c103f381
...
Done. Upserted 10 of 10 companies.
6

Verify records exist (optional)

You can verify a specific company with the Find Unique Record API. Simply send a request with a match property containing the domain of one of the companies the script ran on:
POST /objects/company/find-unique

{
  "domain": "tailscale.com"
}

Conclusion

This approach works for any type of object in Unify, including both standard objects and custom objects that you create. This script can be used as a starting point and customized to send additional values, read from other data sources, or send different types of records.
I