CRM API reference
Every endpoint of the CRM API, what each key needs, the startup each request is for, the errors, and what a webhook posts.
The address of every endpoint starts with https://startupcorners.com/api/v1. Send your key as Authorization: Bearer sc_live_... and JSON with Content-Type: application/json. Answers are JSON. To make a key, see Send leads to your CRM with the API.
Which startup
Add ?entity= to any address with the startup's address name (the part after /s/ in its link) or its id, for example /objects/people/records?entity=kumo-labs. A key that reaches one startup can leave it out.
| Method and address | Needs | What it does |
|---|---|---|
GET /entities | any key | The startups the key reaches: [{ "id", "slug", "name" }]. Needs no ?entity=. |
People, companies, deals
An object is named by its key (people, companies, deals, or a custom one) or its id. Fields are named by their key.
| Method and address | Needs | What it does |
|---|---|---|
GET /objects | records:read | Every object with its fields. |
GET /objects/{object}/attributes | records:read | One object's fields: key, type, and choices. |
GET /objects/{object}/records | records:read | Records, 100 at a time. q searches names; filter is a JSON list of { "attribute", "op", "value" }; sort is key:asc or key:desc; cursor gets the next 100. |
POST /objects/{object}/records | records:write | Adds a record from { "values": { ... } }. 409 duplicate when its email or domain is already on one. |
PUT /objects/{object}/records?matching_attribute=email | records:write | Updates the record with that email (or domain for companies), else adds it. 201 added, 200 updated. |
GET /records/{id} | records:read | One record, its pipeline entries and its latest notes. |
PATCH /records/{id} | records:write | Changes the fields in { "values": { ... } } and leaves the others. |
DELETE /records/{id} | records:write | Deletes the record: it leaves every pipeline, and its email or domain is free again. |
POST /records/{id}/notes | notes:write | Adds a note from { "text": "..." }. A blank line starts a new paragraph. |
Values: a choice by its label ("stage": "Intro sent"), an amount as { "amount": 250000, "currency": "USD" }, a date as 2026-10-28, a person's company as { "domain": "acme.com", "name": "Acme" }.
Pipelines
A pipeline is named by its key (fundraising, customers) or its id.
| Method and address | Needs | What it does |
|---|---|---|
GET /lists | lists:read | The pipelines with their stages. |
GET /lists/{list}/entries | lists:read | Entries, 100 at a time. stage, q and cursor narrow them. |
POST /lists/{list}/entries | lists:write | Puts a record in the pipeline from { "record_id", "stage", "values" } (stage and values optional). 201 added, 200 when it was there already. |
PATCH /entries/{id} | lists:write | Moves it with { "stage": "Term sheet" }, or changes its fields with { "values": { ... } }. |
Webhooks
| Method and address | Needs | What it does |
|---|---|---|
GET /webhooks | webhooks:manage | Your webhooks that reach this startup. |
POST /webhooks | webhooks:manage | A webhook for this startup from { "url": "https://...", "events": [...] }. The answer holds its signing secret, shown only once. |
DELETE /webhooks/{id} | webhooks:manage | Removes it. |
PATCH /webhooks/{id} | webhooks:manage | { "paused": false } turns a paused webhook back on. |
Events: record.created, record.updated, record.deleted, entry.created, entry.updated, entry.moved, entry.deleted, note.created.
Each post is signed in the StartupCorners-Signature header and looks like this:
{
"id": "delivery id",
"type": "entry.moved",
"occurred_at": "2026-10-27T09:30:00.000Z",
"entity": { "id": "...", "slug": "kumo-labs", "name": "Kumo Labs" },
"workspace_id": "the same id as entity.id",
"actor": { "type": "api_key", "id": "..." },
"data": { "record_id": "...", "list_id": "...", "entry_id": "..." }
}
Errors
Errors look like { "error": { "code": "...", "message": "..." } }.
| Status | Code | When |
|---|---|---|
| 400 | entity_required | The key reaches several startups and the address has no ?entity=. The answer lists them in entities. |
| 400 | invalid_request | The body or a query value is not valid. |
| 401 | missing_key, invalid_key | No key, or a wrong or revoked one. |
| 401 | key_owner_left | The key reaches no startup any more: its maker is no longer an owner or admin of any of them. |
| 403 | entity_not_allowed | The key does not reach that startup. |
| 403 | missing_scope | The key may not do this. |
| 403 | go_public_first | A course team's CRM opens once the team goes public. |
| 404 | not_found | The record, pipeline or object does not exist in that startup. |
| 409 | duplicate | The email or domain is on another record, given in existing_record_id. |
| 422 | invalid_value | A field or choice does not exist, or a value is wrong. The message names it. |
| 429 | rate_limited | More than 100 requests in 10 seconds. Wait the seconds in Retry-After. |
AI tools (MCP)
POST https://startupcorners.com/api/mcp is an MCP server for Claude, Cursor and other AI tools, with the same key. Its tools: list_startups, describe_crm, search_records, get_record, upsert_record, update_record, list_pipelines, list_entries, add_to_list, move_entry, add_note. Each takes an optional entity, as the addresses above do.
