> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitsearch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Query a profile

> Search within a single public Orbit profile. Use a profile ID from Search or Profile Read, ask a plain-English question, and receive matching public fun facts with sources.

The profile query endpoint semantically searches indexed public fun facts for one Orbit profile and returns the matching fun facts with their public sources when available. Use it after [Search](/api/search/smart-search) returns a profile, or after [Profile read](/api/search/profile-read) confirms the profile you want to inspect. Each successful request costs 1 credit. Requests that fail validation, hit a rate limit, have insufficient credits, or target a missing profile do not consume credits.

Requests authenticate with a developer API key that has the `search:read` scope. See [Authentication](/authentication).

<RequestExample>
  ```bash curl theme={"dark"}
  curl -X POST "https://api.orbitsearch.com/v2/developer/profiles/usr_a1b2c3d4e5f6/query" \
    -H "Authorization: Bearer sk_orb_REDACTED" \
    -H "Idempotency-Key: request-uuid-or-client-retry-key" \
    -H "Content-Type: application/json" \
    -d '{"query":"founder experience","limit":5}'
  ```

  ```javascript JavaScript theme={"dark"}
  const response = await fetch(
    "https://api.orbitsearch.com/v2/developer/profiles/usr_a1b2c3d4e5f6/query",
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer sk_orb_REDACTED",
        "Idempotency-Key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: "founder experience", limit: 5 }),
    }
  );

  const profileQuery = await response.json();
  console.log(profileQuery.payload.matches);
  ```

  ```python Python theme={"dark"}
  import uuid
  import requests

  response = requests.post(
      "https://api.orbitsearch.com/v2/developer/profiles/usr_a1b2c3d4e5f6/query",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Idempotency-Key": str(uuid.uuid4()),
          "Content-Type": "application/json",
      },
      json={"query": "founder experience", "limit": 5},
  )

  profile_query = response.json()
  print(profile_query["payload"]["matches"])
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"dark"}
  {
    "status": "success",
    "payload": {
      "profile": {
        "id": "orbit-profile-id",
        "displayName": "Ada Lovelace"
      },
      "query": "founder experience",
      "matches": [
        {
          "section": "funFacts",
          "text": "Founded Example Analytics after publishing work on analytical engines.",
          "score": 0.92,
          "sources": [
            {
              "url": "https://example.com/ada",
              "name": "Example Source",
              "title": "Ada Lovelace profile"
            }
          ],
          "createdAt": "2026-02-12T18:44:21.000Z",
          "updatedAt": "2026-05-18T09:17:42.000Z"
        }
      ]
    }
  }
  ```
</ResponseExample>

## Headers

<ParamField header="Authorization" type="string" required>
  `Bearer sk_orb_...` with the `search:read` scope.
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  A UUID or client-generated key for safe retries.
</ParamField>

<Tip>
  Always send an `Idempotency-Key` header. If you omit it, the server uses the request ID, which prevents duplicate charges within a single request but cannot deduplicate a client-side retry. Use a fresh idempotency key for every new profile query.
</Tip>

## Path parameters

<ParamField path="id" type="string" required>
  The profile ID to search. This is the `id` value returned in the `payload.users` array from a [Search](/api/search/smart-search) response or the `payload.id` value from a [Profile read](/api/search/profile-read) response.
</ParamField>

## Body parameters

<ParamField body="query" type="string" required>
  Plain-English question or topic to search for inside this profile. The value is trimmed before searching and must be 1,000 characters or fewer.

  Example: `"founder experience"`, `"music credits"`, `"publications about machine learning"`
</ParamField>

<ParamField body="limit" type="number">
  Maximum number of matching facts to return. Must be a positive integer. Defaults to `10`. Maximum value is `50`.
</ParamField>

## Response

<ResponseField name="status" type="string" required>
  Always `"success"` for a 200 response.
</ResponseField>

<ResponseField name="payload" type="object" required>
  <Expandable title="properties" defaultOpen>
    <ResponseField name="payload.profile" type="object" required>
      The profile that was searched.

      <Expandable title="properties">
        <ResponseField name="payload.profile.id" type="string" required>
          The canonical public profile ID returned by the API. If the requested ID resolves through an alias, this can differ from the original requested ID and can be used again as `:id` for Profile Query or Profile Read.
        </ResponseField>

        <ResponseField name="payload.profile.displayName" type="string">
          The user's public display name.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="payload.query" type="string" required>
      The trimmed query that was searched.
    </ResponseField>

    <ResponseField name="payload.matches" type="array" required>
      Matching public fun facts, ordered by relevance.

      <Expandable title="item properties">
        <ResponseField name="payload.matches[].section" type="string" required>
          Always `funFacts` for profile query matches.
        </ResponseField>

        <ResponseField name="payload.matches[].text" type="string" required>
          Public-safe text for the matched fun fact.
        </ResponseField>

        <ResponseField name="payload.matches[].score" type="number" required>
          Relevance score normalized between `0` and `1`.
        </ResponseField>

        <ResponseField name="payload.matches[].sources" type="array">
          Public sources attached to the matched fun fact, when available.

          <Expandable title="source properties">
            <ResponseField name="payload.matches[].sources[].url" type="string" required>
              Source URL.
            </ResponseField>

            <ResponseField name="payload.matches[].sources[].name" type="string">
              Source name or publisher, when available.
            </ResponseField>

            <ResponseField name="payload.matches[].sources[].title" type="string">
              Source title, when available.
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="payload.matches[].createdAt" type="string">
          ISO timestamp for when the matched fact was created, when available.
        </ResponseField>

        <ResponseField name="payload.matches[].updatedAt" type="string">
          ISO timestamp for when the matched fun fact was last updated, when available.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Profile query returns only public fun facts and their public sources. It does not expose raw contact details, private profile-owner data, internal moderation metadata, raw enrichment records, or Sendit identifiers.
</Note>

## Empty matches

A successful profile query can return an empty `matches` array when the profile exists but no public fun fact matches the query:

```json theme={"dark"}
{
  "status": "success",
  "payload": {
    "profile": {
      "id": "orbit-profile-id",
      "displayName": "Ada Lovelace"
    },
    "query": "favorite espresso machine",
    "matches": []
  }
}
```

Empty matches on an existing profile are a successful request and consume credits. A missing or private profile returns `developer_profile_not_found` and is refunded.

## Credits

Each successful profile query costs **1 credit**. Validation failures, authentication failures, rate-limit failures, insufficient-credit responses, and `developer_profile_not_found` responses do not consume credits.

## Rate limits

**25 requests per second** per API key, with bursts up to **100 requests**. Profile query shares the Search rate-limit bucket with `POST /v2/developer/search` and `POST /v2/developer/search/sse`. A `429` response does not consume credits.

## Error responses

| Status | Code                                     | Description                                                                         |
| ------ | ---------------------------------------- | ----------------------------------------------------------------------------------- |
| `400`  | `developer_profile_query_required`       | `query` is missing, empty, or not a string                                          |
| `400`  | `developer_profile_query_too_long`       | `query` is longer than 1,000 characters                                             |
| `400`  | `developer_profile_query_limit_invalid`  | `limit` is not a positive integer                                                   |
| `400`  | `developer_profile_query_limit_exceeded` | `limit` is greater than 50                                                          |
| `401`  | `missing_api_key`                        | No `Authorization: Bearer sk_orb_...` header was provided                           |
| `402`  | `developer_api_credits_insufficient`     | Your remaining credits are lower than the cost of a profile query                   |
| `403`  | `invalid_api_key`                        | The key is malformed, revoked, expired, or unknown                                  |
| `403`  | `missing_api_key_scope`                  | The key does not have the `search:read` scope                                       |
| `404`  | `developer_profile_not_found`            | The profile ID does not resolve to a public Orbit profile. The request is refunded. |
| `409`  | `developer_api_idempotency_key_conflict` | The idempotency key was reused with different request parameters                    |
| `409`  | `developer_api_idempotency_key_refunded` | The idempotency key belongs to a previously refunded request                        |
| `429`  | `developer_api_key_rate_limited`         | You have exceeded the Search rate limit for this API key                            |

## Search-to-query flow

The example below shows the full flow: run a search, extract the first profile ID from the response, then search inside that profile.

<CodeGroup>
  ```bash curl theme={"dark"}
  SEARCH_RESPONSE=$(
    curl -s -X POST "https://api.orbitsearch.com/v2/developer/search" \
      -H "Authorization: Bearer sk_orb_REDACTED" \
      -H "Content-Type: application/json" \
      -d '{"query":"founders in sf","numUsers":10}'
  )

  PROFILE_ID=$(echo "$SEARCH_RESPONSE" | jq -r '.payload.users[0].id')

  curl -X POST "https://api.orbitsearch.com/v2/developer/profiles/$PROFILE_ID/query" \
    -H "Authorization: Bearer sk_orb_REDACTED" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{"query":"founder experience","limit":5}'
  ```

  ```javascript JavaScript theme={"dark"}
  // Step 1: run a search
  const searchResponse = await fetch("https://api.orbitsearch.com/v2/developer/search", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_orb_REDACTED",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query: "founders in sf", numUsers: 10 }),
  });

  const searchData = await searchResponse.json();
  const profileId = searchData.payload.users[0].id;

  // Step 2: search inside the selected profile
  const profileQueryResponse = await fetch(
    `https://api.orbitsearch.com/v2/developer/profiles/${profileId}/query`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer sk_orb_REDACTED",
        "Idempotency-Key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: "founder experience", limit: 5 }),
    }
  );

  const profileQuery = await profileQueryResponse.json();
  console.log(profileQuery.payload.matches);
  ```

  ```python Python theme={"dark"}
  import uuid
  import requests

  # Step 1: run a search
  search_response = requests.post(
      "https://api.orbitsearch.com/v2/developer/search",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Content-Type": "application/json",
      },
      json={"query": "founders in sf", "numUsers": 10},
  )

  search_data = search_response.json()
  profile_id = search_data["payload"]["users"][0]["id"]

  # Step 2: search inside the selected profile
  profile_query_response = requests.post(
      f"https://api.orbitsearch.com/v2/developer/profiles/{profile_id}/query",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Idempotency-Key": str(uuid.uuid4()),
          "Content-Type": "application/json",
      },
      json={"query": "founder experience", "limit": 5},
  )

  profile_query = profile_query_response.json()
  print(profile_query["payload"]["matches"])
  ```
</CodeGroup>
