# Gelee API (v1)

> Read-only REST API for pulling your Gelee data — campaigns, prospects, stalled leads, analytics, and inbox — into your CRM, warehouse, or dashboards. Designed to be handed directly to an AI agent (Claude Code, etc.) as context.

- **Base URL:** `https://www.gelee.ai/api/v1`
- **Format:** JSON. Successful responses are wrapped as `{ "data": ... }`.
- **Auth:** Bearer API key. Generate one in the Gelee app under **Settings → API**.
- **Versioning:** all paths are prefixed `/api/v1`. Breaking changes ship as a new version.

## Authentication

Send your key as a Bearer token. Each key is tied to a single organization and only ever returns that org's data.

```bash
curl https://www.gelee.ai/api/v1/me \
  -H "Authorization: Bearer gk_xxxxxxxxxxxxxxxx"
```

Missing, malformed, or expired keys return `401`.

## Scopes

Each key is `read` (default) or `read_write`. v1 is read-only today, so any key works; `read_write` is reserved for upcoming write endpoints. Use read-only keys for CRM/reporting integrations.

## Rate limits

- **120 requests/minute** per key.
- `GET /inbox` is limited to **15/minute** (it makes live LinkedIn calls).
- Exceeding a limit returns `429` with `X-RateLimit-*` and `Retry-After` headers. Back off and retry.

## Pagination

List endpoints accept `limit` (default 50, max 200) and `offset`, and return:

```json
{ "data": [ ... ], "pagination": { "limit": 50, "offset": 0, "total": 342 } }
```

## Errors

Errors are returned as:

```json
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }
```

| Status | Meaning |
|--------|---------|
| `401`  | Missing, invalid, or expired API key |
| `404`  | Resource not found (or not in your org) |
| `429`  | Rate limit exceeded — retry after `Retry-After` |
| `5xx`  | Server error — retry with backoff |

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| GET | `/me` | Verify a key and return its org context. |
| GET | `/campaigns` | List campaigns with prospect-status counts. |
| GET | `/campaigns/{id}` | One campaign (sequence, schedule) + status counts. |
| GET | `/prospects` | List prospects. Filters: `campaign_id`, `status`, `limit`, `offset`. |
| GET | `/prospects/{id}` | One prospect by id. |
| GET | `/stalled-leads` | Leads that replied positively then went silent. Params: `days` (default 5), `campaign_id`, `limit`, `offset`. |
| GET | `/analytics` | Org funnel: prospect counts by status. |
| GET | `/analytics/daily` | Per-day outreach counts (invites, connections, replies incl. positive/negative, bookings). Params: `from`, `to`, `campaign_id`, `account_id`. |
| GET | `/inbox` | Recent LinkedIn conversations (live; `limit` ≤ 100). |

### GET /me

```bash
curl https://www.gelee.ai/api/v1/me -H "Authorization: Bearer gk_xxx"
```
```json
{ "data": { "org_id": "…", "scope": "read" } }
```

### GET /campaigns

```json
{ "data": [
    { "id": "…", "name": "Q2 Founders", "status": "active",
      "outreach_type": "connect", "created_at": "…", "updated_at": "…",
      "prospect_counts": { "messaged": 120, "replied": 18, "completed": 9 } }
  ] }
```

### GET /campaigns/{id}

Returns the campaign plus its full `sequence` and `schedule`, and `prospect_counts`.

### GET /prospects

Filters: `campaign_id`, `status`, `limit`, `offset`.

```bash
curl "https://www.gelee.ai/api/v1/prospects?status=replied&limit=100" \
  -H "Authorization: Bearer gk_xxx"
```
```json
{ "data": [
    { "id": "…", "campaign_id": "…", "linkedin_id": "ACoAA…",
      "name": "Jane Doe", "status": "replied", "current_step": 2,
      "headline": "VP Sales @ Acme", "public_identifier": "janedoe",
      "source": "evergreen_post_engager", "created_at": "…" }
  ],
  "pagination": { "limit": 100, "offset": 0, "total": 342 } }
```

### GET /prospects/{id}

Single prospect by Gelee id (the `id` field from the list). `404` if unknown or in another org.

### GET /stalled-leads

Leads that replied positively then went silent for more than `days` (default 5). Useful for surfacing follow-ups that need a human or a multi-channel nudge.

```bash
curl "https://www.gelee.ai/api/v1/stalled-leads?days=5" \
  -H "Authorization: Bearer gk_xxx"
```
```json
{ "data": [
    { "id": "…", "campaign_id": "…", "linkedin_id": "ACoAA…",
      "name": "Jane Doe", "role": "VP Sales", "company": "Acme",
      "headline": "VP Sales @ Acme", "public_identifier": "janedoe",
      "linkedin_url": "https://linkedin.com/in/janedoe",
      "days_since_activity": 9, "last_activity_at": "…",
      "last_stalled_at": "…", "sentiment": "positive" }
  ],
  "pagination": { "limit": 50, "offset": 0, "total": 4 },
  "threshold_days": 5 }
```

### GET /analytics

```json
{ "data": { "campaigns": 6, "total_prospects": 1820,
            "prospects_by_status": { "messaged": 900, "replied": 140, "completed": 60 } } }
```

### GET /analytics/daily

Per-UTC-day outreach counts for daily reporting — one zero-filled row per day.

Params: `from` / `to` (`YYYY-MM-DD`, inclusive; default last 30 days, max 92), optional `campaign_id`, `account_id`.

```bash
curl "https://www.gelee.ai/api/v1/analytics/daily?from=2026-06-01&to=2026-06-30" \
  -H "Authorization: Bearer gk_xxx"
```
```json
{ "data": [
    { "date": "2026-06-01", "invites_sent": 42, "connections_accepted": 11,
      "messages_sent": 23, "replies": 6, "positive_replies": 3,
      "negative_replies": 1, "bookings": 2, "invites_withdrawn": 5 }
  ],
  "meta": { "from": "2026-06-01", "to": "2026-06-30", "timezone": "UTC",
            "note": "positive_replies/negative_replies populate from 2026-07-02 onward" } }
```

`positive_replies` / `negative_replies` come from our AI reply classifier and populate from 2026-07-02 onward; earlier replies count toward `replies` only.

### GET /inbox

Recent LinkedIn conversations across the org's accounts, live from LinkedIn (heavier than the DB endpoints; `limit` ≤ 100, 15 req/min).

## CRM sync patterns

- **Pull:** poll `/prospects` and `/campaigns` on a schedule; upsert into your CRM keyed by `linkedin_id`.
- **Stalled follow-ups:** poll `/stalled-leads` to create tasks for leads that went cold after a positive reply.
- **Push (real-time):** register a webhook in the Gelee app under **Settings → Event Destinations** to receive events (`reply_received`, `call_booked`, `prospect_added`, `conversation_stalled`, …) as they happen.
- **Push (daily digest):** subscribe a destination to `daily_digest` to receive the previous day's counts every morning (13:05 UTC) — ideal for Zapier/Make or a Slack/Discord channel. Payload:

  ```json
  { "event": "daily_digest", "timestamp": "…", "org_id": "…",
    "data": { "report": { "type": "daily_digest", "period": "2026-07-01",
      "stats": { "invites_sent": 42, "connections_accepted": 11, "messages_sent": 23,
                 "replies": 6, "positive_replies": 3, "negative_replies": 1,
                 "bookings": 2, "invites_withdrawn": 5 } } } }
  ```

  Use the destination's **Test** button to fire a realistic sample and map fields in your Zap/scenario before real data flows.
- **Conversions:** `POST https://www.gelee.ai/api/webhooks/conversions` with your key to send conversion events (form fills, signups) back into Gelee.

---

v1 is read-only. Write access (create/update) is planned and will require a `read_write` key. Questions: hello@gelee.ai
