# ChatGPT Scraper API > Source: https://scrape.do/documentation/chatgpt-api/ Send a prompt to ChatGPT and get the assistant reply, citations, rendered output, and tool data as structured JSON The ChatGPT API takes a single text prompt and returns the assistant's reply from `chatgpt.com` as structured JSON. One GET request, no account, no cookies, no browser automation, and no session state to manage on your side. ```bash curl "https://api.scrape.do/plugin/chatgpt/chat?token=$TOKEN&q=ping" ``` > [!NOTE] > **Credit Usage:** Each successful request costs **25 credits**. For bulk processing, use the [Async API with plugins](/documentation/async-api/plugins/). ## Key Features - **One-shot prompt-to-reply**: send a prompt, get the full assistant response back in a single HTTP call. - **Clean text plus rendered output**: get the assistant text with citation markers removed, plus `output.markdown` and `output.html` for display. - **Structured JSON envelope**: the response includes the full message document, citations, source links, tool data, model slug, finish reason, and message / conversation IDs. - **Stateless from your side**: every call is independent. No login, no token refresh, no conversation IDs to track. - **Citation and tool data support**: when ChatGPT searches or returns shopping cards, you get flat `sources[]`, `tool_data.shopping[]`, and the literal `tool_data.search_queries[]` issued upstream. - **Locale signal**: use `geoCode` to ask ChatGPT to answer in the matching language, such as `de` for German or `tr` for Turkish. - **No render fee**: protocol traffic to `chatgpt.com` is included in the per-call price. --- ## Endpoint ``` GET https://api.scrape.do/plugin/chatgpt/chat ``` ### Basic Example **cURL (API mode)** ```bash curl --location --request GET 'https://api.scrape.do/plugin/chatgpt/chat?token=&q=Explain+how+rainbows+form' ``` **Python (API mode)** ```python import requests import json token = "" url = f"https://api.scrape.do/plugin/chatgpt/chat?token={token}&q=Explain+how+rainbows+form" response = requests.request("GET", url) print(json.dumps(response.json(), indent=2)) ``` **Node.js (API mode)** ```javascript const axios = require('axios'); const token = ""; const url = `https://api.scrape.do/plugin/chatgpt/chat?token=${token}&q=Explain+how+rainbows+form`; axios.get(url) .then(response => { console.log(JSON.stringify(response.data, null, 2)); }) .catch(error => { console.error(error); }); ``` *(Go, Ruby, Java, C#, PHP examples are also available on the HTML version.)* ```bash curl "https://api.scrape.do/plugin/chatgpt/chat?token=$TOKEN&q=Explain+how+rainbows+form" ``` ### Request Parameters #### Required | Parameter | Type | Description | |-----------|------|-------------| | `token` | string | Your Scrape.do API authentication token | | `q` | string | Prompt text to send to ChatGPT. Maximum **1024 characters**. URL-encode multi-word prompts | #### Optional | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `model` | string | `auto` | Model selector passed through to ChatGPT, such as `auto`, `gpt-5`, or `gpt-4o`. Unknown or unavailable values fall back to `auto`. | | `geoCode` | ISO-3166-1 alpha-2 | `us` | Country code used as a locale signal. Examples: `us` for English, `de` for German, `tr` for Turkish, `br` for Brazilian Portuguese, and `jp` for Japanese. Invalid codes return `400`. | | `raw_sse` | `0` / `1` | `0` | When `1`, includes the raw upstream stream body in `raw_sse` for debugging. `include_raw=1` is also accepted. | | `shopping_placeholders` | `0` / `1` | `0` | When `1`, each inline product in `output.markdown` is replaced with a `{{shopping:}}` token whose `` matches a `tool_data.shopping[].id`. Lets you render your own product cards by ID instead of relying on position. Markdown-only — `output.html` and `data` are unaffected. See [Render your own product cards](#render-your-own-product-cards). | ### Example Requests ```bash # Basic prompt curl "https://api.scrape.do/plugin/chatgpt/chat?q=hello&token=$TOKEN" # Pin a specific model curl "https://api.scrape.do/plugin/chatgpt/chat?q=summarize+linux+kernel&model=gpt-5&token=$TOKEN" # Ask for a German response curl --get "https://api.scrape.do/plugin/chatgpt/chat" \ --data-urlencode "q=Was sind die wichtigsten Nachrichten heute" \ --data-urlencode "geoCode=de" \ --data-urlencode "token=$TOKEN" # Include the raw stream for debugging curl "https://api.scrape.do/plugin/chatgpt/chat?q=hello&raw_sse=1&token=$TOKEN" # Get inline product placeholders for your own card rendering curl --get "https://api.scrape.do/plugin/chatgpt/chat" \ --data-urlencode "q=Show me Nike Pegasus 41 shoes I can buy, with links" \ --data-urlencode "shopping_placeholders=1" \ --data-urlencode "token=$TOKEN" ``` ### Notes - Prompts longer than 2048 characters are rejected before any model call runs, so no credits are spent on them. - One call returns one assistant reply. There is no multi-turn conversation state; to follow up, include any prior context inside the new `q`. - `geoCode` controls the response language through ChatGPT's locale signal. Web-search sources may still be globally mixed because source selection is decided by ChatGPT. --- ## Response The response includes the full assembled message envelope plus integrator-friendly top-level fields for rendered output, citations, and tool data. ### Top-Level Structure ```json { "prompt": "Explain quantum entanglement in one sentence", "data": { ... }, "output": { "markdown": "...", "html": "..." }, "sources": [ { "url": "...", "title": "...", "snippet": "...", "attribution": "...", "pub_date": 1779209196 } ], "tool_data": { "shopping": [], "ads": [], "search_queries": [] }, "stream_bytes": 18342, "upstream_latency": "4.812s", "pow_inflight": 0 } ``` | Field | Type | Description | |-------|------|-------------| | `prompt` | string | Echo of the submitted prompt | | `data` | object | Full assembled assistant message envelope. `data.message.content.parts[0]` contains clean assistant text with inline citation markers stripped. | | `output.markdown` | string | Cleaned reply as Markdown. Citation tokens are rewritten as proper links when the response cited sources. | | `output.html` | string | `output.markdown` rendered to safe GitHub-flavored HTML. | | `sources` | array | Flat, deduplicated citation and web result list. Empty when ChatGPT did not search the web. | | `tool_data.shopping` | array | Product entries returned by ChatGPT's shopping tool. Each entry includes a stable `id` plus title, URL, price, currency, image URL, attribution, and description when available. Use `id` to match an inline `{{shopping:}}` token (see `shopping_placeholders`) back to its product. | | `tool_data.ads` | array | Sponsored entries when present. Currently reserved and usually empty. | | `tool_data.search_queries` | array | Literal web-search query strings ChatGPT issued, in order. Empty when no search fired. | | `stream_bytes` | integer | Size of the upstream stream body in bytes. | | `upstream_latency` | string | End-to-end upstream call duration, such as `"4.812s"`. | | `raw_sse` | string | Raw upstream stream body. Only present when `raw_sse=1` is set. | | `pow_inflight` | integer | Current in-flight workload counter. Useful as a load signal when running many parallel requests. | `output`, `sources`, and `tool_data` are always present. For a simple non-web-search answer, `sources`, `tool_data.shopping`, `tool_data.ads`, and `tool_data.search_queries` are empty arrays. ### Useful `data` Paths ```text data.message.content.parts[0] → clean assistant text data.message.metadata.content_references[N] → citations data.message.metadata.content_references[N].safe_urls → resolved citation URLs data.message.metadata.finish_details → stop reason data.message.metadata.model_slug → model data.message.status → "finished_successfully" data.message.id → message id data.conversation_id → conversation id ``` ### Example ```json { "prompt": "Explain how rainbows form", "data": { "message": { "id": "f0a2b1c4-...", "status": "finished_successfully", "content": { "content_type": "text", "parts": [ "Rainbows form when sunlight is refracted, reflected, and dispersed inside water droplets ..." ] }, "metadata": { "model_slug": "gpt-5", "finish_details": { "type": "stop" }, "content_references": [ { "type": "webpage", "title": "How Rainbows Form, NOAA SciJinks", "safe_urls": ["https://scijinks.gov/rainbow/"] } ] } }, "conversation_id": "9b8a7c6d-..." }, "output": { "markdown": "Rainbows form when sunlight is refracted, reflected, and dispersed inside water droplets ...", "html": "

Rainbows form when sunlight is refracted, reflected, and dispersed inside water droplets ...

\n" }, "sources": [ { "url": "https://scijinks.gov/rainbow/", "title": "How Rainbows Form, NOAA SciJinks", "snippet": "...", "attribution": "NOAA SciJinks", "pub_date": 0 } ], "tool_data": { "shopping": [], "ads": [], "search_queries": [] }, "stream_bytes": 1422, "upstream_latency": "2.318s", "pow_inflight": 0 } ``` ### Message Envelope Fields | Field | Type | Description | |-------|------|-------------| | `data.message.id` | string | Unique identifier for this assistant message | | `data.message.status` | string | Message status. `"finished_successfully"` for a complete reply | | `data.message.content.parts[0]` | string | The assistant's reply text with inline citation markers removed | | `data.message.metadata.model_slug` | string | Model that produced the reply (e.g., `"gpt-5"`) | | `data.message.metadata.finish_details` | object | Stop reason, e.g., `{ "type": "stop" }` | | `data.message.metadata.content_references` | array | Inline citations. Absent when the model did not cite anything | | `data.conversation_id` | string | Identifier for the conversation this reply belongs to | ### `content_references[]` | Field | Type | Description | |-------|------|-------------| | `type` | string | Reference type (e.g., `"webpage"`, `"image"`) | | `title` | string | Citation title as shown in the reply | | `safe_urls` | string[] | Resolved URLs for the citation. Present on web citations | --- ## Render your own product cards When ChatGPT returns shopping results, `output.markdown` renders inline products as links by default. If you render your own product cards, add `shopping_placeholders=1` and each inline product becomes a `{{shopping:}}` token instead: ```text - {{shopping:36460bc8599e}} - {{shopping:f6d7a830aa6d}} - {{shopping:0f7380b3a66f}} ``` Every `` matches the `id` of an entry in `tool_data.shopping[]`. To render, scan `output.markdown` for `{{shopping:}}` tokens and swap each for your own card, looked up by `id`: ```json "tool_data": { "shopping": [ { "id": "36460bc8599e", "title": "Nike Men's Pegasus 41", "url": "https://www.nike.com/t/...", "price": "$140.00", "image_url": "https://...", "attribution": "Nike + others" } ] } ``` Things to rely on: - **Look up by `id`, not by position.** The same product can appear at more than one inline position (you will see the same token repeated), and the order of `tool_data.shopping[]` does not necessarily follow the order of the text. Counting positions will drift; ID lookup will not. - **Every token resolves.** A token is only emitted when its product has a matching `tool_data.shopping[]` entry. An inline product with no structured match is left as normal text instead of a token. - **`tool_data.shopping[]` can be a superset.** Some products may be listed in `tool_data.shopping[]` without an inline token, because ChatGPT mentioned them in prose. Size your placeholders from the tokens in `output.markdown`, and use `tool_data.shopping[]` to fill in card details by `id`. - **Markdown-only.** This affects `output.markdown` only. `output.html` keeps the normal inline product links, and `data.message.content.parts[0]` is unchanged. --- ## Notes - The envelope follows the streaming message shape used by `chatgpt.com`; we collect the full stream server-side and return the final assembled document. - Citation delimiters that appear inline in the raw stream are stripped before the response is returned. The text in `parts[0]` is clean, citations live separately in `metadata.content_references`, and `sources[]` provides a flat URL list for easier integration. - A reply that ends with `finish_details.type == "stop"` is a normal completion. Other values indicate the model stopped early (e.g., a tool call boundary). - ChatGPT decides whether to search the web or show shopping results on each call. Empty `sources[]` or `tool_data.shopping[]` does not mean the endpoint failed; it means ChatGPT answered without that tool. - Prompts like `Search the web`, `Browse the web`, or `Find live prices` are more likely to trigger web or shopping tools than general explanation prompts. - Typical responses take 3-15 seconds depending on prompt complexity and response length. The endpoint waits for the assistant message to finish and does not stream partial replies. - The per-call timeout is 60 seconds. Shorten unusually long prompts if you receive an upstream timeout. --- ## Error Responses | Status | Body | Cause | |--------|------|-------| | `400` | `{ "error": "token is required" }` | Missing `token` parameter | | `400` | `{ "error": "q (prompt) is required" }` | Missing or empty `q` parameter | | `400` | `{ "error": "q is too long (max 1024 characters)", "message": "..." }` | Prompt exceeds the 1024-character limit | | `400` | `{ "error": "unsupported geoCode" }` | `geoCode` is not a recognized ISO-3166-1 alpha-2 code | | `429` | `{ "error": "server busy", "message": "..." }` | Too many concurrent ChatGPT requests; retry shortly | | `502` | `{ "error": "request failed", "message": "..." }` | Upstream call to ChatGPT failed | | `502` | `{ "error": "empty reply", "message": "..." }` | Upstream returned an empty assistant message; safe to retry | | `504` | `{ "error": "upstream timeout", "message": "..." }` | ChatGPT did not respond within 60 seconds | | `500` | `{ "error": "internal server error" }` | Unexpected internal failure |