ChatGPT Scraper 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.
curl "https://api.scrape.do/plugin/chatgpt/chat?token=$TOKEN&q=ping"Credit Usage: Each successful request costs 25 credits. For bulk processing, use the Async API with 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.markdownandoutput.htmlfor 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 literaltool_data.search_queries[]issued upstream. - Locale signal: use
geoCodeto ask ChatGPT to answer in the matching language, such asdefor German ortrfor Turkish. - No render fee: protocol traffic to
chatgpt.comis included in the per-call price.
Endpoint
GET https://api.scrape.do/plugin/chatgpt/chatBasic Example
curl --location --request GET 'https://api.scrape.do/plugin/chatgpt/chat?token=<SDO-token>&q=Explain+how+rainbows+form'import requests
import json
token = "<SDO-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))const axios = require('axios');
const token = "<SDO-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);
});package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
token := "<SDO-token>"
url := fmt.Sprintf(
"https://api.scrape.do/plugin/chatgpt/chat?token=%s&q=Explain+how+rainbows+form",
token,
)
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}require 'net/http'
require 'json'
token = "<SDO-token>"
url = URI("https://api.scrape.do/plugin/chatgpt/chat?token=#{token}&q=Explain+how+rainbows+form")
response = Net::HTTP.get(url)
puts JSON.pretty_generate(JSON.parse(response))import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ChatGPTChat {
public static void main(String[] args) throws Exception {
String token = "<SDO-token>";
String url = String.format(
"https://api.scrape.do/plugin/chatgpt/chat?token=%s&q=Explain+how+rainbows+form",
token
);
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream())
);
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
System.out.println(response.toString());
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string token = "<SDO-token>";
string url = $"https://api.scrape.do/plugin/chatgpt/chat?token={token}&q=Explain+how+rainbows+form";
using HttpClient client = new HttpClient();
string response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
}<?php
$token = "<SDO-token>";
$url = "https://api.scrape.do/plugin/chatgpt/chat?token={$token}&q=Explain+how+rainbows+form";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
?>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:<id>}} token whose <id> 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. |
Example Requests
# 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 1024 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. geoCodecontrols 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
{
"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:<id>}} 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
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 idExample
{
"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": "<p>Rainbows form when sunlight is refracted, reflected, and dispersed inside water droplets ...</p>\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:<id>}} token instead:
- {{shopping:36460bc8599e}}
- {{shopping:f6d7a830aa6d}}
- {{shopping:0f7380b3a66f}}Every <id> matches the id of an entry in tool_data.shopping[]. To render, scan output.markdown for {{shopping:<id>}} tokens and swap each for your own card, looked up by id:
"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 oftool_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 intool_data.shopping[]without an inline token, because ChatGPT mentioned them in prose. Size your placeholders from the tokens inoutput.markdown, and usetool_data.shopping[]to fill in card details byid.- Markdown-only. This affects
output.markdownonly.output.htmlkeeps the normal inline product links, anddata.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 inmetadata.content_references, andsources[]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[]ortool_data.shopping[]does not mean the endpoint failed; it means ChatGPT answered without that tool. - Prompts like
Search the web,Browse the web, orFind live pricesare 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 |

