# OSINT Deep Search

> Run an agentic OSINT investigation on a person. Disabled by default - access on request.

- **Endpoint:** `POST https://api.nyne.ai/person/osint`
- **Group:** Person APIs (https://api.nyne.ai/documentation/person.md)
- **Auth:** `X-API-Key` + `X-API-Secret` headers
- **Mode:** Asynchronous - submit returns a `request_id` (normally with `202 Accepted`); poll `GET /person/osint?request_id=<request_id>` with the same auth headers until the endpoint reports a terminal response. Where supported, a `callback_url` can notify you when queued work finishes; follow this endpoint's retrieval notes.
- **Access:** Access required - this endpoint is disabled by default on every account. Request access: mailto:support@nyne.ai?subject=OSINT%20API%20access%20request
- **HTML version:** https://api.nyne.ai/documentation/person/osint

Runs an autonomous OSINT investigation across public sources and reconciles everything it finds into a single person report: identity, aliases, usernames, emails, phones, social accounts (including when an account was created and last seen active, where that is known), employment and education history, locations, websites, public content, public records, corporate roles, adverse findings, images, and wallets. Sections with no findings are omitted. `images` mixes photographs of the subject with reverse-image matches (`reverse_match: true`), and a reverse-image match is a page containing a *similar* image, which is not always the same person - where the faces could be compared, `face_match` reports `match`, `different` or `inconclusive`, with `face_match_score` (0-1) giving the strength of the correspondence. An `inconclusive` match is still returned - a comparison that could not decide is not evidence against the page - so check `face_match` before presenting any match as the subject; an absent `face_match` means no comparison result is available, not a negative result, and an unconfirmed entry carries no `confidence` so the score says none while `face_match` says why. A comparison that concluded the faces are `different` is withheld, because that is a finding rather than an absence of one. Requires at least one direct identifier (`email`, `phone`, `social_media_url`, `username`, or `domain`), *or* a `name` together with a `company` or `city`. `mode` selects how hard the agent works the search (`shallow`, `medium` by default, or `deep`) and therefore what it costs; the retired `depth` parameter is rejected with `400` rather than silently downgraded. A search keeps running server-side for up to 30 minutes, so poll about every 10 seconds or supply a `callback_url`. If that window closes while the run is still working, whatever it had already assessed is returned and billed like any other result - reaching the limit is how depth is bounded, not a failure - including records it had gathered but not yet judged, which arrive as leads at zero confidence with `UNADJUDICATED_CANDIDATES` raised. A run cut short before it had assessed anything at all returns nothing and is reported as failed, because records no adjudication has looked at are not findings about your subject; those are never returned. A `failed` request returns `result` too whenever the run got far enough to produce diagnostics, holding `report_quality` and `reconciliation_summary` and nothing else - so the error is not all you get. It never carries anything about the person: a run that produced data about the subject is a success, so an error and person data never arrive together. A run cut short before adjudication began has no diagnostics either, and then `result` is absent entirely. `result.report_quality` describes the report you are holding so you can filter it in code rather than by eye: `flags` is a list of stable terms naming what is present (`DISPUTED_RECORDS_PRESENT` when an independent review rejected some returned records, `UNCONFIRMED_RECORDS_PRESENT` when review could not confirm them, `UNADJUDICATED_CANDIDATES` when records were gathered that nobody adjudicated - those are returned at the lowest confidence among `emails`, `phones`, `usernames`, `social_profiles`, `locations`, `wallets` and `images`, so treat them as leads - every adjudicated item carries a confidence above zero and every lead carries exactly zero, so `confidence > 0` reliably keeps only the adjudicated results. An item is never scored down for something we merely could not label: an account on a site we cannot name keeps its confidence and reports `platform: unspecified` instead, `ROLES_DERIVED` when a record's category was inferred from its type, `LOW_CONFIDENCE_RECORDS_PRESENT` when some records scored below the floor the policy considers strong and are returned at their own score rather than withheld), sized by `disputed_count` and `unconfirmed_count`. Records are never withheld to express a flag - a disputed record is returned with its confidence capped at 0.1 and an unconfirmed one at 0.35 instead of being removed, so you can filter by score or by term. Zero - or the field being absent, since a zero double is not transmitted - means one thing only: nobody adjudicated the record. `confidence > 0` therefore keeps just the adjudicated results in any JSON consumer, and a strict typed reader should treat a missing confidence as zero. A confirmed record carries the adjudicating model's own confidence, usually well above those caps but not guaranteed to be, so treat the numbers as a scale to threshold on rather than as ranked tiers. A report with no caveats omits `flags` entirely. `resolution_status` says how the investigation ended. `complete` means records were accepted and the report holds findings - it is reported whenever anything was accepted, *including* when the search was cut short by its time budget or salvaged after a failure, so read `unreviewed_count` and `flags` rather than this field to judge how complete the sweep was. `no_match` means the search ran and concluded none of the candidates was the subject - a finding rather than a failure, and not billed when the report holds nothing but that verdict. `budget_exhausted` and `adjudication_failed` both mean nothing was *accepted*. None of these three is decided by `resolution_status` alone: whether a request succeeds, is billed, and answers `200` depends only on whether the report carries data about the person, so a run that gathered leads before it ran out of budget or failed adjudication still returns them, still bills, and still answers `200` - with the leads at zero confidence and `UNADJUDICATED_CANDIDATES` raised. Only a report with nothing in it at all is reported as failed, and that one is never billed. `degraded` means findings alongside candidates never judged either way, which you see on a partially-complete result rather than a finished search, and `unspecified` means the run had not reached a resolution when the result was stored - you see that on a partial salvaged from a search that failed, was cancelled, or was cut short by the poll window partway, where the findings present are still real. **This endpoint is disabled by default for every account** - see Access below before you integrate it.

## Access

OSINT Deep Search is off by default on every account, so there is nothing to test until it is switched on for yours: submits return `403 PRODUCT_NOT_AVAILABLE` and the playground below stays disabled. Request access and we will enable it for your workspace, then the same API key you already use starts working here.

Request access: mailto:support@nyne.ai?subject=OSINT%20API%20access%20request

## Authentication

All `/person/*` and `/company/*` endpoints authenticate with an API key and secret sent as HTTP headers on every request: `X-API-Key` and `X-API-Secret`. Create keys from your Nyne.ai dashboard and keep the secret server-side. Rate limits: 100 requests/minute, 1,000 requests/hour. Full guide: https://api.nyne.ai/documentation/authentication.md

## Parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `email` | string | no | Email identifier. Validated for format. | "jane.doe@acme.com" |
| `phone` | string | no | Phone identifier. Separators are stripped; 7-15 digits with an optional leading `+`. | "+1-555-123-4567" |
| `social_media_url` | string \| array | no | A LinkedIn, Twitter/X, Instagram, or Facebook profile URL, or an array of up to 3 such URLs (one per platform). Other hosts are rejected with `400`. | "https://linkedin.com/in/janedoe" |
| `username` | string | no | A handle to pivot from, resolved across platforms. | "janedoe" |
| `domain` | string | no | A domain tied to the subject, e.g. a personal site or an employer domain. | "janedoe.dev" |
| `name` | string | no | Full name. Requires `company` or `city` when used without a direct identifier. | "Jane Doe" |
| `company` | string | no | Employer; pairs with `name` for name-based search. | "Acme" |
| `city` | string | no | City; pairs with `name`. Truncated to 100 chars. | "Austin" |
| `mode` | string | no | Search effort tier, and what the request costs: `shallow`, `medium` (default), or `deep`. Case-insensitive; any other value returns `400`. See Credit usage for the per-tier price. | "medium" |
| `callback_url` | string | no | If set, the completed result is POSTed here when the job finishes. Must be a valid HTTP(S) URL on an allowed host when a callback allow-list is configured. | "https://hooks.example.com/result" |

## Polling for the result

A newly queued submit normally returns `202 Accepted` with a `request_id`. Poll the same path until `status` is `completed` (results stay available afterwards). Where supported, pass a `callback_url` on the submit to receive a webhook when queued work finishes; keep the documented GET as a recovery and retrieval path:

```bash
curl "https://api.nyne.ai/person/osint?request_id=<request_id>" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET"
```

## Credit usage

| Item | Credits | Condition |
| --- | --- | --- |
| OSINT Deep Search (medium) | 100 | Default mode - charged once, when a report with data is delivered |
| Shallow mode | 50 | Charged instead of the default when mode is shallow |
| Deep mode | 150 | Charged instead of the default when mode is deep |

## Responses

| Code | Meaning |
| --- | --- |
| `200` | Status poll result - returns the current request status, plus the report or error once terminal |
| `202` | Request queued - poll the status endpoint (or wait for the callback) with the returned request_id |
| `400` | INVALID_PARAMETERS - malformed JSON, no usable identifier, an unsupported social URL, an invalid mode, or the retired depth parameter |
| `401` | Missing or invalid API credentials |
| `402` | insufficient_credits - not enough credits for the requested mode |
| `403` | PRODUCT_NOT_AVAILABLE - OSINT is not enabled for this account (request access), or ACCESS_DENIED when the request_id belongs to another API key |
| `404` | NOT_FOUND - no matching request for this request_id |
| `429` | RATE_LIMIT_EXCEEDED |
| `500` | INTERNAL_ERROR - request failed unexpectedly |
| `502` | UPSTREAM_UNAVAILABLE - the OSINT service could not start or track the search |
| `503` | SERVICE_UNAVAILABLE - the endpoint is temporarily unavailable |

## Example request

### cURL

```bash
curl -X POST https://api.nyne.ai/person/osint \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane.doe@acme.com",
    "social_media_url": "https://linkedin.com/in/janedoe",
    "mode": "medium"
  }'
```

### Python

```python
import requests

resp = requests.post(
    "https://api.nyne.ai/person/osint",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "X-API-Secret": "YOUR_API_SECRET",
    },
    json={
        "email": "jane.doe@acme.com",
        "social_media_url": "https://linkedin.com/in/janedoe",
        "mode": "medium",
    },
)
data = resp.json()
print(data)
```

### Node

```javascript
const resp = await fetch("https://api.nyne.ai/person/osint", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "X-API-Secret": "YOUR_API_SECRET",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "jane.doe@acme.com",
    social_media_url: "https://linkedin.com/in/janedoe",
    mode: "medium",
  }),
});
const data = await resp.json();
console.log(data);
```

### PHP

```php
<?php
$ch = curl_init("https://api.nyne.ai/person/osint");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "X-API-Key: YOUR_API_KEY",
    "X-API-Secret: YOUR_API_SECRET",
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "email" => "jane.doe@acme.com",
    "social_media_url" => "https://linkedin.com/in/janedoe",
    "mode" => "medium",
  ]),
]);
$data = json_decode(curl_exec($ch), true);
print_r($data);
```

## Initial 202 response

```json
{
  "request_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271",
  "status": "processing",
  "message": "OSINT deep search request submitted. Use GET with request_id to check status.",
  "created_on": "2026-01-15T10:45:02"
}
```

## Completed poll response

```json
{
  "request_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271",
  "status": "completed",
  "completed": true,
  "created_on": "2026-01-15T10:45:02",
  "completed_on": "2026-01-15T10:52:31",
  "result": {
    "identity": {
      "name": "Jane Doe",
      "kind": "person",
      "labels": [
        "product leadership"
      ],
      "confidence": 0.92,
      "identifiers": [
        {
          "value": "jane.doe@acme.com",
          "confidence": 0.95,
          "best": true
        }
      ]
    },
    "aliases": [
      {
        "value": "Jane A. Doe",
        "kind": "legal",
        "confidence": 0.71
      }
    ],
    "usernames": [
      {
        "value": "janedoe",
        "platform": "github",
        "url": "https://github.com/janedoe",
        "confidence": 0.88
      }
    ],
    "emails": [
      {
        "value": "jane.doe@acme.com",
        "kind": "work",
        "verified": true,
        "confidence": 0.95
      }
    ],
    "phones": [
      {
        "value": "+15551234567",
        "kind": "mobile",
        "verified": false,
        "confidence": 0.62
      }
    ],
    "social_profiles": [
      {
        "platform": "linkedin",
        "url": "https://linkedin.com/in/janedoe",
        "username": "janedoe",
        "display_name": "Jane Doe",
        "follower_count": 4200,
        "confidence": 0.93,
        "first_seen": "2026-01-15T10:47:10Z",
        "last_seen": "2026-01-15T10:47:10Z",
        "usage": {
          "created_at": "2011-03-08T00:00:00Z",
          "last_seen_at": "2026-01-10T00:00:00Z"
        },
        "registered": true,
        "verified": false,
        "platform_variables": {
          "headline": "VP of Product at Acme"
        }
      }
    ],
    "employment_history": [
      {
        "company": "Acme",
        "title": "VP of Product",
        "start_date": "2023-04",
        "current": true,
        "confidence": 0.9
      }
    ],
    "education_history": [
      {
        "institution": "State University",
        "degree": "BSc",
        "field": "Computer Science",
        "confidence": 0.7
      }
    ],
    "locations": [
      {
        "city": "Austin",
        "region": "TX",
        "country": "US",
        "current": true,
        "confidence": 0.81
      }
    ],
    "websites": [
      {
        "url": "https://janedoe.dev",
        "title": "Jane Doe",
        "domain": "janedoe.dev",
        "confidence": 0.76
      }
    ],
    "public_content": [
      {
        "title": "Jane Doe on shipping AI products",
        "url": "https://example.com/interview",
        "description": "Interview covering product strategy at Acme.",
        "published_at": "2025-11-02T00:00:00Z",
        "confidence": 0.65
      }
    ],
    "images": [
      {
        "url": "https://records.example/subject.jpg",
        "confidence": 0.9
      },
      {
        "url": "https://social.example/p/abc",
        "source": "social.example",
        "description": "Matched profile page",
        "reverse_match": true,
        "similarity": 94.5,
        "source_image_url": "https://records.example/subject.jpg",
        "thumbnail_url": "https://thumbs.example/abc.jpg",
        "face_match": "match",
        "face_match_score": 0.87,
        "confidence": 0.93
      },
      {
        "url": "https://blog.example/post",
        "source": "blog.example",
        "reverse_match": true,
        "similarity": 71.2,
        "face_match": "different",
        "face_match_score": 0.04,
        "confidence": 0.4
      }
    ],
    "confidence": 0.88,
    "report_quality": {
      "resolution_status": "complete",
      "confidence_semantics": "heuristic_evidence_strength",
      "accepted_evidence_count": 14,
      "unreviewed_count": 2,
      "flags": [
        "UNADJUDICATED_CANDIDATES",
        "UNCONFIRMED_RECORDS_PRESENT"
      ],
      "disputed_count": 0,
      "unconfirmed_count": 3,
      "retry_count": 0,
      "fallback_count": 0,
      "policy_version": "identity-publication-v1"
    }
  }
}
```

---

All documentation pages are available as Markdown by appending `.md` to their URL. Index: https://api.nyne.ai/llms.txt
