# Galileo API

Are you a bot? You're welcome to follow https://galileo.hibarri.com/developers.md

> Find and verify work emails, search people and companies, and keep leads in sync. Same credits as the Galileo app. Human-readable docs: https://galileo.hibarri.com/developers

Base URL: `https://api.hibarri.com/api/galileo/v1`

Send your secret key on every request:

```
Authorization: Bearer $GALILEO_API_KEY
```

`X-API-Key: gal_live_…` works too. A key acts as the teammate who created it and spends shared workspace credits. Keep it on your server. The API does not accept browser calls from other sites. Revoke a leaked key from Settings and it stops working immediately.

## Quickstart

1. Create a key in Settings → API & Webhooks. It is shown once.
2. Save it as `GALILEO_API_KEY`.
3. Call any endpoint under `https://api.hibarri.com/api/galileo/v1`. Every free account starts with 500 credits.

Requests and responses are JSON. Responses that spend or check credits include the remaining balance as `credits`.

Example — find one verified work email:

```bash
curl -X POST "https://api.hibarri.com/api/galileo/v1/emails/find" \
  -H "Authorization: Bearer $GALILEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fullName":"Dylan Field","domain":"figma.com"}'
```

## Credits

The API uses the same credits and prices as the Galileo app. Each endpoint lists its cost. Searches that find nothing are free. A request that needs more credits than the workspace has returns `402 INSUFFICIENT_CREDITS` and charges nothing.

Credit packs start at $9 for 1,800 credits (https://galileo.hibarri.com/pricing), with volume discounts down to $0.20 per 100.

## Rate limits

Limits are per key, per minute, and depend on the workspace plan.

| Plan | Requests / minute |
| --- | --- |
| Free Search | 30 |
| Telescope | 120 |
| Hubble | 600 |

Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds). Over the limit you get `429` with a `Retry-After` header.

## Errors

Errors use HTTP status codes and a JSON body with a readable `message` and, where useful, a machine-readable `code`.

```json
{
  "message": "You've used all your credits. Buy more from Pricing or Settings.",
  "code": "INSUFFICIENT_CREDITS"
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | INVALID_BODY / INVALID_EMAIL | A required field is missing or malformed. |
| 401 | MISSING_API_KEY / INVALID_API_KEY | No key sent, or the key was revoked. |
| 402 | INSUFFICIENT_CREDITS | The workspace is out of credits. Top up to continue. |
| 404 | NOT_FOUND | No such record in this workspace, or no such endpoint. |
| 429 | RATE_LIMITED | Over your per-minute limit. Wait for Retry-After seconds. |
| 503 | — | An upstream data source is briefly unavailable. Retry with backoff. |

## Using with AI agents

Each endpoint does one job and returns small JSON, so it maps onto a tool call. Run the HTTP request in your tool handler and pass the JSON back to the model.

```json
{
  "name": "find_work_email",
  "description": "Find and verify a person's work email with Galileo. Use when you know the person's name and their company or company domain. Returns the best verified email, or found=false.",
  "input_schema": {
    "type": "object",
    "properties": {
      "fullName": {
        "type": "string",
        "description": "The person's full name"
      },
      "company": {
        "type": "string",
        "description": "Company name, if the domain is unknown"
      },
      "domain": {
        "type": "string",
        "description": "Company domain, e.g. figma.com"
      }
    },
    "required": [
      "fullName"
    ]
  }
}
```

- Give the agent its own key so you can see its usage and revoke it on its own.
- `found: false` is a normal answer, not an error.
- For lists, call `POST /emails/verify/bulk` once and wait for the `bulk_verification.completed` webhook.
- Handle `402` and `429` in your tool code before the model sees them.

## Account

### Get account

`GET /account`

Returns the workspace your key belongs to, its plan, remaining credits and your rate limit. Good for a health check.

Cost: Free

Example response (200):

```json
{
  "workspace": {
    "id": "66f1…",
    "name": "Acme AI",
    "plan": "telescope"
  },
  "credits": 18420,
  "rateLimitPerMinute": 120,
  "apiKey": {
    "id": "66f2…",
    "name": "Production",
    "prefix": "gal_live_4f9Q"
  }
}
```

## Emails

### Find an email

`POST /emails/find`

Finds one person's work email. Galileo works out the company's domain, tries the likely address patterns, verifies each one over SMTP and returns the best match plus up to 3 alternatives.

Cost: 2 credits when an email is found, free otherwise

Body:

- `fullName` (string), required — Full name. Or send firstName + lastName.
- `company` (string) — Company name. Used to find the domain when no domain is given.
- `domain` (string) — Company domain, e.g. figma.com. Faster and more accurate than company alone.

Example request:

```json
{
  "fullName": "Dylan Field",
  "domain": "figma.com"
}
```

Example response (200):

```json
{
  "domain": "figma.com",
  "found": true,
  "best": {
    "email": "dfield@figma.com",
    "status": "valid",
    "score": 95
  },
  "top": [
    {
      "email": "dfield@figma.com",
      "status": "valid",
      "score": 95
    },
    {
      "email": "dylan@figma.com",
      "status": "valid",
      "score": 90
    }
  ],
  "credits": 18418
}
```
### Verify an email

`POST /emails/verify`

Checks whether one address can receive mail. Status is valid, invalid, risky (a catch-all domain, so the mailbox can’t be confirmed) or unknown.

Cost: 1 credit per check

Body:

- `email` (string), required — The address to check.

Example request:

```json
{
  "email": "ceo@stripe.com"
}
```

Example response (200):

```json
{
  "email": "ceo@stripe.com",
  "status": "risky",
  "score": 60,
  "detail": "Domain is catch-all (accepts any address); mailbox cannot be confirmed",
  "credits": 18417
}
```
### Start a bulk verification

`POST /emails/verify/bulk`

Verifies up to 1,000 addresses in the background. Duplicates and malformed addresses are dropped before charging. Poll the job, or subscribe to the bulk_verification.completed webhook.

Cost: 1 credit per unique email, charged upfront; refunded for any the job doesn’t reach

Body:

- `emails` (string[]), required — Up to 1,000 addresses.

Example request:

```json
{
  "emails": [
    "ada@example.com",
    "grace@example.org"
  ]
}
```

Example response (202):

```json
{
  "id": "66f3a1…",
  "status": "processing",
  "total": 2,
  "processed": 0,
  "counts": {
    "valid": 0,
    "invalid": 0,
    "risky": 0,
    "unknown": 0
  },
  "credits": 18415
}
```
### Get a bulk verification

`GET /emails/verify/bulk/:id`

Progress and results of a bulk job. status is processing, completed or failed.

Cost: Free

Example response (200):

```json
{
  "id": "66f3a1…",
  "status": "completed",
  "total": 2,
  "processed": 2,
  "counts": {
    "valid": 1,
    "invalid": 1,
    "risky": 0,
    "unknown": 0
  },
  "results": [
    {
      "email": "ada@example.com",
      "status": "valid",
      "score": 95,
      "detail": null
    },
    {
      "email": "grace@example.org",
      "status": "invalid",
      "score": 0,
      "detail": "Mailbox does not exist"
    }
  ]
}
```

## People

### Search people

`POST /people/search`

Finds people by title, company, industry and location. New people are saved to your workspace as leads, so they show up in Galileo and in /leads. Page through with resultOffset = nextOffset and queryVariant = nextVariant while hasMore is true.

Cost: 1 credit per new person saved. People already in your workspace are free.

Body:

- `jobTitle` (string) — e.g. "Head of Growth".
- `company` (string) — Employer name.
- `industry` (string) — e.g. "Mining", "Software".
- `location` (string) — City or region.
- `country` (string) — Country name.
- `keywords` (string) — Free-text keywords.
- `fullName` (string) — Search for one named person.
- `limit` (number) — Max people per page.
- `resultOffset` (number) — From the previous nextOffset.
- `queryVariant` (number) — From the previous nextVariant.

Example request:

```json
{
  "jobTitle": "Head of Growth",
  "industry": "Software",
  "location": "Cape Town"
}
```

Example response (200):

```json
{
  "people": [
    {
      "fullName": "Thandi Mokoena",
      "jobTitle": "Head of Growth",
      "company": "Yoco",
      "linkedinUrl": "https://www.linkedin.com/in/…",
      "_saved": true,
      "_leadId": "66f1…"
    }
  ],
  "autoSaved": 1,
  "skippedForCredits": 0,
  "hasMore": true,
  "nextOffset": 10,
  "nextVariant": 0,
  "credits": 18414
}
```
### Enrich a LinkedIn profile

`POST /people/enrich`

Turns a public LinkedIn profile URL into a person with a verified work email, and saves them as a lead.

Cost: 1 credit for the lead, plus 2 when an email is found

Body:

- `url` (string), required — A linkedin.com/in/… profile URL.
- `domain` (string) — Override the company domain used for the email.

Example request:

```json
{
  "url": "https://www.linkedin.com/in/dylanfield"
}
```

Example response (201):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "cold",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z"
  },
  "person": {
    "fullName": "Dylan Field",
    "company": "Figma",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield"
  },
  "discovery": {
    "domain": "figma.com",
    "found": true,
    "best": {
      "email": "dfield@figma.com",
      "status": "valid",
      "score": 95
    }
  },
  "credits": 18411
}
```

## Companies

### Search companies

`POST /companies/search`

Finds companies by name, industry or keyword, optionally in a location. Pages the same way as people search.

Cost: Free

Body:

- `term` (string), required — Company name, industry or keyword.
- `location` (string) — City, region or country.
- `limit` (number) — 5–40 per page (default 30).
- `resultOffset` (number) — From the previous nextOffset.
- `queryVariant` (number) — From the previous nextVariant.

Example request:

```json
{
  "term": "coffee roasters",
  "location": "Cape Town"
}
```

Example response (200):

```json
{
  "companies": [
    {
      "name": "Truth Coffee",
      "website": "truth.coffee",
      "category": "Food & Beverages",
      "employees": "51-200"
    }
  ],
  "hasMore": true,
  "nextOffset": 30,
  "nextVariant": 0
}
```
### Find company emails

`POST /companies/emails`

Verified emails for a company: addresses published on its website plus common role inboxes (info@, sales@ and so on).

Cost: 2 credits when at least one email is found, free otherwise

Body:

- `domain` (string) — Company domain. Send this or companyName.
- `companyName` (string) — Used to find the domain when none is given.

Example request:

```json
{
  "domain": "truth.coffee"
}
```

Example response (200):

```json
{
  "domain": "truth.coffee",
  "emails": [
    {
      "email": "hello@truth.coffee",
      "status": "valid",
      "score": 92,
      "fromSite": true
    }
  ],
  "siteEmails": [
    "hello@truth.coffee"
  ],
  "credits": 18409
}
```
### Get company info

`POST /companies/info`

Best-effort industry, headcount, LinkedIn page and (optionally) website for a company name.

Cost: Free

Body:

- `company` (string), required — Company name.
- `includeWebsite` (boolean) — Also resolve the website. Slower.

Example request:

```json
{
  "company": "Figma",
  "includeWebsite": true
}
```

Example response (200):

```json
{
  "company": "Figma",
  "category": "Software Development",
  "employees": "1,001-5,000",
  "website": "figma.com",
  "linkedinUrl": "https://www.linkedin.com/company/figma"
}
```

## Leads

### List leads

`GET /leads`

Your saved people, newest first.

Cost: Free

Query parameters:

- `page` (number) — Page number, from 1.
- `limit` (number) — 1–100 per page (default 50).
- `q` (string) — Search name, company, email and more.
- `stage` (string) — cold, approached, warm, meeting or sale.
- `listId` (string) — Only leads in this saved list.
- `scope` (string) — people (default) or places, for Google Maps businesses.

Example response (200):

```json
{
  "leads": [
    {
      "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
      "name": "Dylan Field",
      "title": "CEO",
      "company": "Figma",
      "email": "dfield@figma.com",
      "emailVerification": {
        "status": "valid",
        "label": "Valid",
        "score": 90,
        "checkedAt": "2026-09-29T10:12:00.000Z",
        "detail": "Mailbox exists",
        "serverResponse": null
      },
      "phone": "",
      "website": "figma.com",
      "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
      "stage": "cold",
      "source": "api",
      "createdAt": "2026-09-29T10:12:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "pages": 1,
  "limit": 50
}
```
### Create a lead

`POST /leads`

Saves a person or company to your workspace.

Cost: 1 credit

Body:

- `name` (string) — Person or company name.
- `title` (string) — Job title.
- `company` (string) — Company name.
- `email` (string) — Work email.
- `phone` (string) — Phone number.
- `website` (string) — Company website.
- `linkedinUrl` (string) — LinkedIn profile URL.
- `location` (string) — City or region.
- `stage` (string) — cold (default), approached, warm, meeting or sale.
- `notes` (string) — Free-text notes.
- `source` (string) — Your label for where it came from, e.g. "api".

Example request:

```json
{
  "name": "Dylan Field",
  "company": "Figma",
  "title": "CEO",
  "source": "api"
}
```

Example response (201):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "cold",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z"
  },
  "credits": 18408
}
```
### Get a lead

`GET /leads/:id`

One lead by id.

Cost: Free

Example response (200):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "cold",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z"
  }
}
```
### Update a lead

`PATCH /leads/:id`

Changes only the fields you send. Changing the email resets its verification status to unverified.

Cost: Free

Body:

- `…` — Any field from Create a lead, plus doNotContact (boolean).

Example request:

```json
{
  "stage": "meeting",
  "notes": "Booked for Tuesday"
}
```

Example response (200):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "meeting",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z",
    "notes": "Booked for Tuesday"
  }
}
```
### Delete a lead

`DELETE /leads/:id`

Permanently deletes the lead.

Cost: Free

Example response (200):

```json
{
  "message": "Lead deleted"
}
```
### Find a lead's email

`POST /leads/:id/find-email`

Runs Find an email using the lead's name and company, and saves the best result onto the lead.

Cost: 2 credits when an email is found, free otherwise

Example response (200):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "cold",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z"
  },
  "discovery": {
    "domain": "figma.com",
    "found": true
  },
  "credits": 18406
}
```
### Re-verify a lead's email

`POST /leads/:id/verify-email`

Checks the lead's current email again and saves the new status.

Cost: Free

Example response (200):

```json
{
  "lead": {
    "_id": "66f1c2a9e4b0a1b2c3d4e5f6",
    "name": "Dylan Field",
    "title": "CEO",
    "company": "Figma",
    "email": "dfield@figma.com",
    "emailVerification": {
      "status": "valid",
      "label": "Valid",
      "score": 90,
      "checkedAt": "2026-09-29T10:12:00.000Z",
      "detail": "Mailbox exists",
      "serverResponse": null
    },
    "phone": "",
    "website": "figma.com",
    "linkedinUrl": "https://www.linkedin.com/in/dylanfield",
    "stage": "cold",
    "source": "api",
    "createdAt": "2026-09-29T10:12:00.000Z"
  },
  "verification": {
    "email": "dfield@figma.com",
    "status": "valid",
    "score": 95
  }
}
```

## Manage webhooks

### List webhooks

`GET /webhooks`

Your webhook endpoints, plus every event type you can subscribe to.

Cost: Free

Example response (200):

```json
{
  "webhooks": [
    {
      "id": "66f4…",
      "url": "https://example.com/hooks/galileo",
      "events": [
        "*"
      ],
      "secret": "whsec_…",
      "active": true
    }
  ],
  "events": [
    "lead.created",
    "…"
  ]
}
```
### Create a webhook

`POST /webhooks`

Registers an https endpoint. The response includes the signing secret.

Cost: Free

Body:

- `url` (string), required — A public https URL.
- `events` (string[]) — Event types, or ["*"] for all (the default).
- `description` (string) — A note for your team.

Example request:

```json
{
  "url": "https://example.com/hooks/galileo",
  "events": [
    "email.found",
    "bulk_verification.completed"
  ]
}
```

Example response (201):

```json
{
  "webhook": {
    "id": "66f4…",
    "url": "https://example.com/hooks/galileo",
    "events": [
      "email.found",
      "bulk_verification.completed"
    ],
    "secret": "whsec_…",
    "active": true
  }
}
```
### Update a webhook

`PATCH /webhooks/:id`

Change the URL, events or description, or pause it with active: false. Setting active: true also re-enables an endpoint Galileo switched off after repeated failures.

Cost: Free

Body:

- `url` (string) — New URL.
- `events` (string[]) — New event list.
- `active` (boolean) — Pause or resume deliveries.

Example request:

```json
{
  "active": true
}
```

Example response (200):

```json
{
  "webhook": {
    "id": "66f4…",
    "active": true
  }
}
```
### Delete a webhook

`DELETE /webhooks/:id`

Stops all deliveries to the endpoint, including pending retries.

Cost: Free

Example response (200):

```json
{
  "deleted": true,
  "id": "66f4…"
}
```
### Send a test event

`POST /webhooks/:id/test`

Sends a webhook.test event right away and returns how your endpoint responded.

Cost: Free

Example response (200):

```json
{
  "delivery": {
    "type": "webhook.test",
    "status": "succeeded",
    "responseStatus": 200,
    "durationMs": 84
  }
}
```

## Webhooks

Webhooks tell your server when something happens in the workspace, including actions taken in the Galileo app. Register an https endpoint in Settings → API & Webhooks or with `POST /webhooks`.

Galileo sends each event as a `POST` with a JSON body. Reply with any `2xx` within 10 seconds. Headers on every delivery:

- `Galileo-Event` — the event type
- `Galileo-Delivery` — a unique id for this delivery attempt
- `Galileo-Signature` — `t=<unix time>,v1=<hex HMAC>`

Use the event `id` to ignore duplicates. A retry sends the same id again.

```json
{
  "id": "evt_5b1f0c2e9a7d4c3b8e6f1a2d",
  "type": "email.found",
  "created": "2026-09-29T10:12:03.511Z",
  "workspaceId": "66f1c2a9e4b0a1b2c3d4e5f0",
  "data": {
    "fullName": "Dylan Field",
    "company": "Figma",
    "domain": "figma.com",
    "email": "dfield@figma.com",
    "status": "valid",
    "score": 95,
    "leadId": "66f1c2a9e4b0a1b2c3d4e5f6"
  }
}
```

### Event types

| Type | When |
| --- | --- |
| `lead.created` | A lead was saved — from the app, a search, a LinkedIn enrich or the API. |
| `lead.updated` | A lead was edited. |
| `lead.deleted` | A lead was deleted. data holds only its id. |
| `email.found` | Find-email turned up a verified address. |
| `email.verified` | A single email check finished. |
| `bulk_verification.completed` | An API bulk verification job finished. Includes every result. |
| `verify_batch.completed` | A Scheduler batch verification finished. |
| `form_filler.campaign.completed` | A Form Filler campaign finished all its sites. |
| `webhook.test` | Sent only when you press Send test event or call the test endpoint. |

### Verifying signatures

Each endpoint has its own signing secret (`whsec_…`). Galileo signs the string `<t>.<raw body>` with HMAC-SHA256. Compare your signature to `v1` in constant time, and reject timestamps more than 5 minutes old. Sign the raw request bytes. Re-serialising parsed JSON changes the bytes and breaks the signature.

### Retries

A failed delivery is retried 5 more times: after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After 20 deliveries in a row fail completely, Galileo disables the endpoint. Fix it, then resume it with `PATCH /webhooks/:id` and `{"active": true}`.

Settings keeps 30 days of deliveries and lets you redeliver any of them.
