> **Shapeless documentation index**
> Fetch https://shapelessai.com/llms.txt to discover every page before exploring further.
> Every page also answers Markdown: append `.md` to its path, or send `Accept: text/markdown`.

# Shapeless Identity

Resolve a handle or a domain into the person or organization behind it, pull their posts, sponsors and ads one ring at a time, and price every pull before it runs.

*Verified 2026-10-25. Source: https://shapelessai.com/docs/identity*

Shapeless Identity is one graph of who is who on social. A **person** owns **accounts**, works at
or runs **organizations**, and publishes **assets** (posts, reels, ads). Every link carries the
evidence that found it, and every fact the **pull** (one vendor call, with its cost) that
produced it. The graph is shared: a person someone already resolved opens for free.

Three verbs: **resolve** a seed into a node, **expand** a node by one ring, **read** what is held.

## Resolve: who is this?

```bash
curl -sN https://shapelessai.com/api/identity/resolve \
  -H "Authorization: Bearer $SHAPELESS_API_KEY" -H "Content-Type: application/json" \
  -d '{"seed":"https://www.instagram.com/millee","budgetUsd":1}'
```

```bash
shapeless identity resolve https://www.instagram.com/millee --budget 1
```

A seed is a handle, a profile URL, a personal site, a LinkedIn company URL or a bare domain
(`shapelessai.com`). `kind: "person" | "organization"` overrides the guess. The resolve finds the
person and their accounts, then reads those accounts' profiles and labels the person, all inside
one budget. A seed the graph already knows answers at once with `costUsd: 0`; `force: true` runs it
again.

## Expand: one more ring

```bash
curl -sN https://shapelessai.com/api/identity/expand \
  -H "Authorization: Bearer $SHAPELESS_API_KEY" -H "Content-Type: application/json" \
  -d '{"node":"account:<id>","expansion":"posts","since":"2026-04-01","maxItems":200,"budgetUsd":0.5}'
```

```bash
shapeless identity expand account:<id> posts --since 2026-04-01 --max-items 200 --budget 0.5
```

| Expansion | On | Pulls |
| --- | --- | --- |
| `profile` | account, person, organization | Profile fields and avatar; for a person every account's; for an organization its site, its own accounts, its LinkedIn id and logo. |
| `posts` | account | What it published, newest first, with metrics. |
| `sponsors` | account | The brands that paid it: paid-partnership labels, the branded-content library. |
| `ads` | organization | Its ads from the Meta, Google and LinkedIn ad libraries (TikTok where open). |
| `creators` | organization | The creators around it: paid ones, partners, and the ones its accounts feature. |
| `metrics` | asset | A fresh snapshot of one post's numbers. |
| `labels` | person, organization | What it is (category, niches), read by a model from what the graph holds; for a person also what `similar` ranks by. |

## The controls

| Field | Range | What it does |
| --- | --- | --- |
| `budgetUsd` | resolve 0.5 to 5 (default 2); expand 0.01 to 2 (default 0.5) | The most the pull may spend, at engine cost. Outside the range it is clamped. |
| `since`, `until` | ISO days, at most 10 years back | `posts`: the publish-date range to read. `until` defaults to now. |
| `sinceMonths` | 1 to 36 (default 12) | `posts`: shorthand for `since` = now minus this many months. |
| `maxItems` | 25 to 1000 (default 300) | `posts`: the most posts one pull lands. |
| `force` | `true` / `false` | Pull even when fresh, and the whole `posts` range rather than only its gaps. |
| `kind` | `person` / `organization` | resolve: what the seed is. |

A `posts` pull remembers the date ranges it read. The next one reads only the gaps, forward or
backward, unless `force`.

**Freshness.** An expansion pulled recently answers `done` with `costUsd: 0` unless `force`:
profiles and metrics are fresh for a day; sponsors, ads, creators and labels for a week. Posts follow
the account's own rhythm: half the median gap between its newest posts, held to 6 hours to 7 days
(a day until three posts are held). `pull-estimate` says when (`nextRefreshAt`). A posts pull that
names `since` or `until` skips the age check and reads only the unread gaps.

## Cost before you click

```bash
shapeless identity estimate account:<id> posts --since 2026-04-01
curl -s "https://shapelessai.com/api/identity/pull-estimate?node=account:<id>&expansion=posts&since=2026-04-01" \
  -H "Authorization: Bearer $SHAPELESS_API_KEY"
```

`pull-estimate` prices a pull before it runs, from the price list and what the graph already
holds, never a vendor: `estimateUsd`, `capUsd` (the most it can spend at these settings), and for
posts `alreadyHeld`, `coveredFrom`, `coveredTo` and the priced `gaps`. For `creators` it lists
`steps`, cheapest first: the bios and posts the graph already holds (free), one posts page of the
brand's creator-feature account, then a vendor's index of paid creators, each priced. Free.

A resolve or an expand holds its budget on your credit wallet first (`X-Identity-Hold-Usd` says how
much), and settles at what the `done` event's `costUsd` says, charged like every other run: engine
cost on the free plan, three times it on a paid plan. The rest comes back. A wallet that cannot
hold the budget is a `402` naming `holdUsd` and `balanceUsd`, before anything is pulled; lower
`budgetUsd` or top up. A pull you abort, or one that ends in `error`, pays only for the vendor
calls that already ran. A `done` says where the money went (`breakdown`: `modelUsd` for the
model's turns, `vendorUsd` for the vendors' calls); a resolve's `error` also carries `costUsd`,
`breakdown` and `stoppedBy` (budget, steps, refusal, stuck, abort, error or undelivered).

## The stream

A resolve, an expand and `POST /api/identity/post` answer `application/x-ndjson`, one event per
line, as it happens:

```jsonc
{"type":"status","label":"Reading the profile"}
{"type":"pull","pull":{"id":"...","vendor":"...","status":"ok","items":48,"costUsd":0.012}}
{"type":"graph","nodes":[...],"edges":[...]}
{"type":"done","anchor":"account:<id>","costUsd":0.012,"summary":"posts of account:<id> pulled."}
```

It ends with exactly one `done` (the anchor node and the bill) or one `error`. A `pull` repeats per
id as it moves from `running` to `ok`. The CLI prints each line as it lands; the MCP tools
`identity_resolve` and `identity_expand` return the anchor, the cost, the pulls and every node seen.

## Read

All free, `read` scope:

| Method | Path | Answers |
| --- | --- | --- |
| GET | `/api/identity/search?q=&limit=` | Known people, accounts and organizations, with labels. Search before you resolve. |
| GET | `/api/identity/node/[id]` | One node: fields, edges with evidence, pulls, freshness, posts coverage, metric history. |
| GET | `/api/identity/graph?anchor=` | A node, its first ring and the edges among them. |
| GET | `/api/identity/people?q=&sort=&category=&niche=&scope=&limit=` | Everyone, sorted `updated`, `followers`, `assets` or `name`. `scope=mine` is what you track and one edge out. |
| GET | `/api/identity/facets?scope=` | Categories, niches and organizations with counts. |
| GET | `/api/identity/similar?anchor=person:<id>&limit=&scope=` | People like one person, each with a `why`. |
| GET | `/api/identity/library?anchor=&sort=&platform=&limit=&offset=` | Posts held, sorted `newest`, `views`, `multiplier` (against the account's own median) or `likes`. |
| GET/POST/DELETE | `/api/identity/tracked` | What you track as `me`, `competitor`, `reference` or `watch` (POST and DELETE cost `write`). A resolve or an expand tracks its node as `watch`. |

Node ids are `person:<uuid>`, `account:<uuid>`, `organization:<uuid>` and
`asset:<platform>:<postId>`. Every route and schema is in the
[OpenAPI document](/api/openapi.json); the MCP tools are `identity_search`, `identity_node`,
`identity_people`, `identity_similar`, `identity_library`, `identity_tracked`, `identity_estimate`,
`identity_resolve` and `identity_expand`.
