# Public API v1 — Contacts, Companies, Leads & Notes

_Developers_

A small, stable REST API for pushing data into CRMown from enrichment tools, forms, and your own systems. Writes are upserts matched on natural keys, so re-sending the same rows is safe. Interactive reference: https://app.crmown.io/api/v1/docs — OpenAPI JSON: https://app.crmown.io/api/v1/docs/json.

## Steps
1. GET /v1/contacts/search?email=… (contacts:read) — find a contact by email (case-insensitive). Returns id, email, firstName, lastName, phone, jobTitle, tags, leadScore and stage, or 404.
2. POST /v1/contacts/upsert (contacts:write) — create or update a contact. Fields: email, firstName, lastName, phone, title, companyDomain, tags[], custom{}, source. Needs either an email, or companyDomain + firstName + lastName. Returns { id, created, matchedBy } where matchedBy is "email", "domain_name" or null (new).
3. POST /v1/contacts/{id}/notes (notes:write) — add a note: { "body": "…" }.
4. POST /v1/companies/upsert (companies:write) — create or update a company by domain: { domain, name, industry, size, custom }.
5. POST /v1/leads (leads:write) — create/update the contact and put them on a pipeline: { email, source, score (0–100), pipeline (pipeline name; default pipeline if omitted), custom }. Returns { id, created, pipelineItemId, warning }.

## Tips
- Matching: by email first (lowercased, trimmed). With no email, the same first and last name at the same company domain matches. If several match, the oldest contact wins.
- Updates overwrite the fields you send; tags are added, never removed; an existing email is never replaced. companyDomain links to an existing company but does not create one — upsert the company first.
- Where the record came from: send an X-Source header (e.g. X-Source: clay) or a "source" field — letters, digits, dot, dash, underscore, up to 40 characters. It is set on NEW contacts only; the default is "api".
- Domains are normalised: https://www.Example.com/about becomes example.com.
- The API never sets email/SMS marketing consent — record consent in CRMown where you collected it.
- Leads are placed on the first stage of the pipeline, and skipped if the contact is already on it. A warning of no_pipeline_found or pipeline_has_no_stages tells you the contact was saved but not placed.
- Every contact upsert fires the contact.created or contact.updated webhook with source "api".
- Errors are JSON { error, code }: unauthorized (401), wrong_api_surface / insufficient_scope (403), not_found (404), missing_identity (400), rate_limited (429).

### Upsert a contact
```
curl -X POST https://app.crmown.io/api/v1/contacts/upsert \
  -H "Authorization: Bearer ck_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Source: clay" \
  -d '{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace","title":"CTO","companyDomain":"example.com","tags":["webinar"]}'
```

### Create a lead on a pipeline
```
curl -X POST https://app.crmown.io/api/v1/leads \
  -H "Authorization: Bearer ck_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","pipeline":"Sales","score":80,"source":"website"}'
```
