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

# Ask ChatGPT and Gemini through one endpoint, priced only for what actually comes back.

> Send prompts to ChatGPT and Gemini with country-level targeting through three REST endpoints. Credits, submitting queries, polling for results, and error codes.

The LLM API sends your prompts to ChatGPT and Gemini with country-level targeting, then hands you the rendered answer through three plain REST endpoints. A credit is spent only when a query actually succeeds - a failed one refunds itself automatically, no ticket required.

| **api.nodemaven.com** | **x-api-key header** | **dashboard → Profile** | **JSON** |
| - | - | - | - |
| submit and poll | request auth | where your key lives | everywhere |

## Quickstart flow

Four steps, start to finish - from copying your key to reading back an answer.

<Steps>
  <Step title="Get your API key">
    Your key lives in the dashboard's Profile section. Every call below sends it as a Bearer-style header, prefixed `x-api-key` instead of `Bearer`.

    `dashboard → Profile`
  </Step>

  <Step title="Submit your questions">
    One call can carry several prompts across several platforms and up to 10 locations - one row is created per question × platform × location.

    `POST /submit/`
  </Step>

  <Step title="Poll for results">
    Every row starts `pending`. Poll the same batch until each one turns `success` or `failed`.

    `GET /results/{request_id}/`
  </Step>

  <Step title="Read the answer">
    A successful row's `raw_response.text` is the rendered answer. Failed rows refund their own credit.

    `raw_response.text`
  </Step>
</Steps>

<Note>
  **This is the whole surface.** Three endpoints, total: check your balance any time before you submit, submit a batch, poll it. There's no fourth thing to learn.
</Note>

## Authentication

One key, one header - but note the prefix isn't `Bearer`.

<CardGroup cols={2}>
  <Card title="Where to get it">
    Your API key is shown in the **Profile** section of the NodeMaven dashboard - the same key used across NodeMaven's base API, not a separate token per product.
  </Card>

  <Card title="How to send it">
    `Authorization: x-api-key <your key>` on every request. Note the literal prefix is `x-api-key`, not `Bearer`.
  </Card>
</CardGroup>

```bash theme={null}
# Every request uses the key from your dashboard's Profile section
curl https://api.nodemaven.com/api/v2/base/llm/balance/ \
  -H "Authorization: x-api-key YOUR_API_KEY"
```

<Warning>
  **Purchase a credit pack before you integrate.** All three endpoints below - including balance - return `403` until your account has at least one completed credit-pack purchase. Free monthly credits alone don't unlock the API; that gate only applies to the dashboard's own GEO page.
</Warning>

## Errors and status codes

The body shape depends on *why* a request failed - a validation problem reports field-by-field, an operational one reports a single message.

<CodeGroup>
  ```json 400 - bad input (field-keyed) theme={null}
  {
    "country": [
      "ChatGPT and Gemini are not available in 'ru'"
    ]
  }
  ```

  ```json 402 - insufficient credits theme={null}
  {
    "error":
      "Insufficient credits: required 3, available 1"
  }
  ```
</CodeGroup>

| Status | Meaning | Notes |
| - | - | - |
| `400` | Bad request | Malformed JSON, too many or too-long questions, an unknown platform, a duplicated platform, or a country the model isn't available in. Body is field-keyed: `{"<field>": ["…"]}`. |
| `401` | Unauthorized | Missing or invalid `Authorization` header. |
| `402` | Payment required | Not enough credits to cover this submit's full question × platform count. Nothing is written or charged. |
| `403` | Forbidden | No completed credit-pack purchase on the account yet. Body: `{"detail": "…"}`. |
| `404` | Not found | `request_id` unknown, or belongs to another account - ownership never leaks as a distinct error. Body: `{"error": "Not found"}`. |

## Limits

Defaults below - ask support if your workload needs more headroom.

| Limit | Default |
| - | - |
| Questions per submit | `20` |
| Characters per question | `2,047` |
| Submit rate | `20 / minute` |
| Balance and results rate | `100 / minute` |
| Free credits | `2 / calendar month` |

## Platforms and targeting

Every question is sent to one or more platforms, aimed at one or more locations (each with a required country and, optionally, a region and city).

| Platform value | Sent as | Status |
| - | - | - |
| `chatgpt` | ChatGPT | Available |
| `gemini` | Gemini | Available |
| `perplexity` | Perplexity | Currently unavailable |

`perplexity` is still a valid value in the request schema, but the platform is currently unavailable - leave it out of `platforms` for now. It'll be listed as **Available** here once that changes.

<Warning>
  **Not every country is available.** `country` is a required, lowercase, 2-letter ISO code - but a small set of countries return a `400` because the underlying models aren't offered there at all. Check the response's `country` field for the exact message rather than hardcoding a list, since availability is enforced server-side and can change.
</Warning>

`region` and `city` are both optional and both normalized for you - send a region's display name or its code, and a city in any case, and the request still resolves correctly. Each row in the response carries the exact location it ran against, so a multi-location submit can be split back apart client-side by grouping on `country`/`region`/`city`.

## Credits

One credit is spent per question × platform pair, upfront - and refunded automatically if that specific row fails.

### `GET` /api/v2/base/llm/balance/

<ParamField header="Authorization" type="string" required>
  `x-api-key <your key>`
</ParamField>

Your current balance. Also grants this month's free credits automatically, the first time you check in a given calendar month - nothing to request separately.

```json 200 Response theme={null}
{ "balance": 7 }
```

| Status | |
| - | - |
| `200` | ok |
| `403` | no purchase yet |

## Queries

Submit a batch, then poll the same `request_id` until every row leaves `pending`.

### `POST` /api/v2/base/llm/submit/

<ParamField header="Authorization" type="string" required>
  `x-api-key <your key>`
</ParamField>

Charges `questions × platforms` credits and returns immediately - every row starts `pending`, nothing here is the final answer yet.

| Field | | Description |
| - | - | - |
| `questions` | **required** | Array of prompt strings, up to 20 of them, 2,047 characters each. |
| `platforms` | **required** | Array of `chatgpt` / `gemini` / `perplexity`, no duplicates - see availability above. |
| `locations` | **required** | Array of `{country, region, city}` objects, 1 to 10 of them - one row is created per question × platform × location. `country` is required per location (lowercase 2-letter ISO); `region`/`city` are optional. See Platforms and targeting above. |

<CodeGroup>
  ```json Request body theme={null}
  {
    "questions": [
      "What are the best residential proxy providers in 2026?"
    ],
    "platforms": ["chatgpt", "gemini"],
    "locations": [
      { "country": "us", "region": "CA", "city": "Los Angeles" }
    ]
  }
  ```

  ```json 200 Response theme={null}
  {
    "request_id": "7c1e…b0a4",
    "results": [
      {
        "id": "9f2a…",
        "platform": "chatgpt",
        "status": "pending",
        "raw_response": null
      },
      "…"
    ]
  }
  ```
</CodeGroup>

| Status | |
| - | - |
| `200` | accepted, all rows pending |
| `400` | bad questions / platform / country |
| `402` | insufficient credits |
| `403` | no purchase yet |

### `GET` /api/v2/base/llm/results/\{request\_id}/

<ParamField header="Authorization" type="string" required>
  `x-api-key <your key>`
</ParamField>

Every row from one submit, by its shared `request_id`. Call this again a few seconds later for whatever's still `pending`.

```json 200 Response theme={null}
{
  "request_id": "7c1e…b0a4",
  "results": [
    {
      "id": "9f2a…", "platform": "chatgpt",
      "question": "What are the best residential proxy providers in 2026?",
      "country": "us", "region": "CA", "city": "Los Angeles",
      "status": "success",
      "raw_response": {
        "text": "For scale, the most consistently well-reviewed…",
        "used_html_fallback": false,
        "sources": [
          { "title": "…", "url": "https://…", "publisher": "…", "snippet": "…" }
        ]
      },
      "created": "2026-09-08T09:14:02Z",
      "updated": "2026-09-08T09:15:19Z"
    },
    {
      "id": "c48d…", "platform": "gemini",
      "…": "…", "status": "pending", "raw_response": null
    }
  ]
}
```

A `failed` row's credit has already been refunded by the time you see it - `raw_response` stays `null`, there's nothing else to read.

<Note>
  **`raw_response.sources` is best-effort, not guaranteed.** ChatGPT populates it when (and only when) it actually ran a web search for that answer - plenty of prompts never trigger that, and get an empty `[]` back, which is normal, not an error. Gemini only ever returns citations through this same field (never inline in `text`), and does so noticeably less often than ChatGPT - an empty list from Gemini isn't a sign anything's broken, and shorter or plainer answers from Gemini than ChatGPT are also expected, not a bug.
</Note>

| Status | |
| - | - |
| `200` | check each row's own status |
| `404` | unknown request\_id, or not yours |

## Polling for results

There's no webhook - results only ever arrive by asking again.

<CardGroup cols={2}>
  <Card title="How long it takes">
    Most rows resolve in **20 to 90 seconds**, occasionally longer - a real Chrome session is rendering the answer on the other end. Poll every few seconds rather than continuously.
  </Card>

  <Card title="The three states">
    `pending` → still rendering. `success` → read `raw_response.text`. `failed` → already refunded, nothing to retry manually.
  </Card>

  <Card title="Billing timing">
    Credits are charged at **submit**, not on success - a row that later fails refunds itself the moment it's marked `failed`, before you even poll.
  </Card>

  <Card title="Batching platforms">
    One `request_id` can hold rows across every platform you asked for - poll it once and read whichever rows have already turned.
  </Card>
</CardGroup>

## Full walkthrough

Every step above, chained into one script: check your balance, ask two platforms the same question, wait it out, then read both answers.

```bash theme={null}
#!/bin/bash
# Your key, copied from the dashboard's Profile section.
TOKEN="$NM_API_KEY"

# 1. check your balance (this also grants this month's free credits, if due)
curl -s https://api.nodemaven.com/api/v2/base/llm/balance/ \
  -H "Authorization: x-api-key $TOKEN"

# 2. submit one question to two platforms, targeted at Los Angeles
RESPONSE=$(curl -s -X POST https://api.nodemaven.com/api/v2/base/llm/submit/ \
  -H "Authorization: x-api-key $TOKEN" -H "Content-Type: application/json" \
  -d '{"questions":["What are the best residential proxy providers in 2026?"],"platforms":["chatgpt","gemini"],"locations":[{"country":"us","region":"CA","city":"Los Angeles"}]}')
REQUEST_ID=$(echo $RESPONSE | jq -r .request_id)

# 3. poll until nothing is left pending
until [ "$(curl -s https://api.nodemaven.com/api/v2/base/llm/results/$REQUEST_ID/ \
  -H "Authorization: x-api-key $TOKEN" | jq '[.results[] | select(.status=="pending")] | length')" = "0" ]; do
  sleep 5
done

# 4. read both answers
curl -s https://api.nodemaven.com/api/v2/base/llm/results/$REQUEST_ID/ \
  -H "Authorization: x-api-key $TOKEN" \
  | jq -r '.results[] | "\(.platform) [\(.status)]: \(.raw_response.text // "-")"'
```

***

For any questions contact [support@nodemaven.com](mailto:support@nodemaven.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.