Skip to content

Developer API

Machine access for enterprise customers. An API key belongs to a workspace, not a person, and the same confidentiality rules the website enforces apply to it.

The rule to design around

Confidential data is returned only for listings your workspace holds live data-room access to. For everything else `confidential` is null and `confidentialWithheldReason` says why. The seller's own range/hidden choice also applies — `metrics.disclosure` tells you which mode a listing is in.

Authentication

Create a key from your workspace page. It is shown once — we store only a SHA-256 hash and have no way to retrieve it. If you lose it, revoke and issue a new one.

curl https://exithunt.app/api/v1/opportunities?limit=50 \
  -H "Authorization: Bearer eh_live_..."

Scopes: opportunities:read, deals:read, webhooks:manage

GET /api/v1/opportunities

Active opportunities, cursor-paginated. Parameters: `limit` (max 200), `cursor`, `businessType`. The response carries `data` and `nextCursor`.

Webhooks

Events: LISTING_PUBLISHED, LISTING_UPDATED, OFFER_RECEIVED, DEAL_STAGE_CHANGED. Every delivery is signed — verify before you process it.

import { createHmac, timingSafeEqual } from "node:crypto";

// Headers: x-exithunt-event, x-exithunt-timestamp, x-exithunt-signature
function verify(secret, rawBody, timestamp, signature) {
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

There is no retry queue. If your endpoint doesn't answer, the delivery is recorded FAILED with its response code and we do not try again — catch up by polling the API. We say so plainly because designing against an at-least-once guarantee that doesn't exist is worse than having no webhooks at all.

For enterprise access, see pricing.