Back to Blog

How to Connect Scrapeless to ChatGPT With a Custom GPT Action

James Thompson
James Thompson

Scraping and Proxy Management Expert

21-Sep-2026

TL;DR:

  • ChatGPT cannot take an API-key MCP server as a connector. Developer-mode MCP accepts OAuth 2.1 or no authentication, and OpenAI's own documentation says ChatGPT "cannot present custom API keys".
  • The route that works is a custom GPT Action: an OpenAPI schema plus API-key authentication on a custom header.
  • The header is x-api-token, not Authorization: Bearer. Set the auth type to API Key, then Custom, then that header name.
  • Ask for Markdown, not HTML. The same page is 8,676 characters as Markdown against 50,403 as HTML — an 83% cut in the context the model spends on markup.
  • response_type does it only alongside js_render: true. Leave js_render out and the same request returns 50,368 characters of HTML with HTTP 200. outputFormat is accepted and silently ignored, returning the full 50,403 characters of HTML.
  • The schema below passes openapi-spec-validator against OpenAPI 3.1.0, and the request it describes was executed live: {code: 200, data: string}.
  • Get a key on the Scrapeless free plan before you start.

Ask ChatGPT about a page it has not seen and you get a summary of its training data or a browsing result you cannot control. An Action changes the arrangement: you hand the model one HTTP operation it can call, with parameters you defined, against an API you chose.

The first thing to settle is which mechanism ChatGPT will actually accept, because the obvious answer is wrong.

Why This Is an Action and Not an MCP Connector

Every other major client takes the Scrapeless MCP server as a remote HTTP connector with the key on a header. ChatGPT does not, and it is worth seeing why before building around it.

The endpoint requires a static header. Called without one:

text Copy
POST https://api.scrapeless.com/mcp   (no auth)
-> HTTP 401
   body: Unauthorized: Missing x-api-token header
   www-authenticate: None

That missing www-authenticate header matters. Under the HTTP authentication framework a 401 is where a server advertises how to authenticate, and a client looking for an OAuth challenge finds nothing to follow. Nor is there any OAuth metadata to discover:

text Copy
/.well-known/oauth-protected-resource       404
/.well-known/oauth-authorization-server     404
/.well-known/oauth-protected-resource/mcp   404

The Model Context Protocol specification allows either arrangement — a bare token on a header is a perfectly ordinary MCP deployment. The constraint is on ChatGPT's side: its developer-mode connectors support OAuth 2.1 or no authentication, and OpenAI's documentation states plainly that ChatGPT cannot present custom API keys.

So there is no URL to paste. The supported path for a keyed HTTP API is a GPT Action, which does support API-key authentication with a header name you choose.

Prerequisites

  • A ChatGPT plan that includes creating GPTs.
  • A Scrapeless API key.
  • No hosting, no proxy, no local process. The Action calls api.scrapeless.com directly.

Step 1: The OpenAPI Schema

An Action is an OpenAPI document describing one or more operations. This one describes a single operation: fetch a rendered page and return it as Markdown.

yaml Copy
openapi: 3.1.0
info:
  title: Scrapeless Universal Scraping API
  description: Fetch a fully rendered web page and return it as Markdown or HTML.
  version: "1.0.0"
servers:
  - url: https://api.scrapeless.com
paths:
  /api/v2/unlocker/request:
    post:
      operationId: scrapeWebPage
      summary: Fetch a web page with JavaScript rendering and return it as Markdown
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [actor, input]
              properties:
                actor:
                  type: string
                  enum: [unlocker.webunlocker]
                  description: The Scrapeless actor to run.
                input:
                  type: object
                  required: [url, js_render, response_type]
                  properties:
                    url:
                      type: string
                      format: uri
                      description: The page to fetch.
                    js_render:
                      type: boolean
                      enum: [true]
                      default: true
                      description: Must be true. response_type only takes effect when JavaScript rendering is on.
                    response_type:
                      type: string
                      enum: [markdown, html]
                      default: markdown
                      description: Return the page as Markdown or raw HTML.
      responses:
        "200":
          description: The rendered page.
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  data:
                    type: string
                    description: The rendered page, as Markdown or HTML.
        "401":
          description: Missing or invalid API token.
components:
  securitySchemes:
    scrapelessApiKey:
      type: apiKey
      in: header
      name: x-api-token
security:
  - scrapelessApiKey: []

Three deliberate choices in there.

The actor is an enum with one value rather than a free string. A model given a free-text field will eventually invent an actor name; an enum makes the only valid value the only option.

operationId is scrapeWebPage, and that is the name you reference in the GPT's instructions. A vague id produces vague tool selection.

response_type defaults to markdown, for the reason in step 3, and both it and js_render are listed as required. A schema default is documentation: it does not make the model send the field, and the API's own default for js_render is off.

Validating before pasting is worth the thirty seconds — the OpenAPI 3.1.0 specification is strict about structure, and the builder's error messages are terse:

bash Copy
pip install openapi-spec-validator
bash Copy
python3 -c "
from openapi_spec_validator import validate
from openapi_spec_validator.readers import read_from_filename
spec, _ = read_from_filename('scrapeless-action.yaml')
validate(spec)
print('valid')"
text Copy
valid

Step 2: Authentication

In the GPT builder, open the Action's authentication panel and set:

Field Value
Authentication Type API Key
Auth Type Custom
Custom Header Name x-api-token
API Key your Scrapeless key

The default under API Key is Bearer, which sends Authorization: Bearer <key>. Scrapeless reads x-api-token and nothing else, so leaving the default produces a 401 that the builder only surfaces when the Action is first called — after the schema has already validated.

Note: the builder is a web UI, so this step was not executed as part of the verification for this article. Every claim about the API itself — the schema, the header name, the response shape and the sizes below — comes from live calls against api.scrapeless.com.

Step 3: Ask for Markdown

This one setting decides how much of the model's context the connector spends before it has read anything, and the difference is measurable.

The same category page, fetched twice:

text Copy
response_type=markdown      8,676 chars
default (html)             50,403 chars

Markdown is 83% smaller. A GPT Action's response goes into the model's context, so returning HTML spends most of that budget on tags, inline scripts and attributes the model will ignore.

There is a trap next to it. outputFormat looks like it should work and is accepted without complaint:

text Copy
input.response_type = "markdown"   ->  8,676 chars  (markdown)
input.outputFormat  = "markdown"   -> 50,403 chars  (HTML)

The second call succeeded, returned HTTP 200, and quietly gave back HTML because outputFormat is not a parameter the actor reads. An unknown key that is ignored rather than rejected is the harder kind of bug — nothing fails, the output is just wrong-shaped and nearly six times larger than you budgeted for.

The second trap is quieter. response_type only takes effect when JavaScript rendering is on, and the API's default is off. Send response_type: "markdown" without js_render: true and the call returns HTTP 200 with 50,368 characters of HTML, with no error and no warning. The schema above pins js_render to true and lists it as required for exactly this reason, and the instructions below name both fields.

Building this now? The Scrapeless free plan covers enough requests to test the Action end to end.

Step 4: Instructions That Call It

The schema gives the model a capability; the instructions decide when it reaches for it. Name the operation explicitly:

text Copy
When the user gives you a URL, or asks about the current contents of a
specific page, call scrapeWebPage with that URL, js_render true and
response_type "markdown". Do not answer from memory when a URL is present.

Return what the page says, and quote the exact figures it contains rather
than paraphrasing them. If scrapeWebPage reports a 401, tell the user the
API key is missing or misconfigured and stop.

The first paragraph binds the tool to a trigger. Without it, a model with a browsing capability of its own will sometimes use that instead and produce results your schema had no part in.

What Comes Back

The response envelope is two fields, and the schema above declares both:

json Copy
{
  "code": 200,
  "data": "-   [Home](https://books.toscrape.com/index.html)\n-   [Books](...)\n..."
}

Verified against the live API with exactly the body the schema describes:

text Copy
HTTP 200
response keys      : ['code', 'data']
code               : 200 (int)
data               : str, 50403 chars
schema match       : code=integer:True  data=string:True

code is Scrapeless's own status, distinct from the HTTP status — both were 200 here. data is a single string, which is why the model receives a document rather than a structure; if you want fields, ask for them in the instructions or parse them yourself downstream.

Conclusion

The connector is one operation and one header. ChatGPT will not take an API-key MCP server — that is a platform limit, confirmed by a 401 with no OAuth challenge, three 404s where the metadata would be, and OpenAI's own statement — so the mechanism is an Action, and the mechanism is not the hard part.

The two choices that decide whether it works well are both small. Set the custom header to x-api-token, because the Bearer default fails at call time rather than at setup. And set response_type to markdown alongside js_render: true, because 8,676 characters of Markdown leaves room to think where 50,403 characters of HTML does not — and because the plausible-looking outputFormat is accepted, ignored, and hands back the larger one.

For the same API driven from code instead of a GPT, our ChatGPT web scraping guide covers the model-plus-fetch pattern, the Universal Scraping API page describes the actor behind the operation, the docs carry the full parameter reference, and pricing lists what each call costs.

Ready to give ChatGPT a fetch you control? Start with the Scrapeless free plan and paste the schema in.

FAQ

Q: Can ChatGPT connect to an MCP server?

Yes, but only one using OAuth 2.1 or no authentication. Developer-mode connectors cannot present a static API key, which OpenAI's documentation states directly. A server like the Scrapeless MCP endpoint, which authenticates on an x-api-token header and publishes no OAuth metadata, therefore cannot be added as a ChatGPT connector — a GPT Action is the supported route for it.

Q: Why does my GPT Action return a 401?

Most often the header name. The API Key auth type defaults to Bearer, which sends Authorization: Bearer <key>; Scrapeless reads x-api-token. Set Auth Type to Custom and the header name to x-api-token. The schema validates either way, so this surfaces on the first call rather than at setup.

Q: What OpenAPI version do GPT Actions need?

The schema above is OpenAPI 3.1.0 and validates against that specification. Keep the document minimal — one server URL, explicit operationId values, and no $ref indirection you do not need — because the builder's parser is stricter and its errors less specific than a dedicated validator's.

Q: How do I stop the Action from filling the model's context?

Return Markdown. Setting response_type to markdown, with js_render: true in the same request, took the same page from 50,403 characters to 8,676, and the Action's response is spent from the conversation's context budget. Also narrow the schema: one operation with a small parameter set gives the model less room to construct an expensive call.

Q: Why did my outputFormat parameter do nothing?

Because it is not a parameter the actor reads. The request still returned HTTP 200 and the full HTML — 50,403 characters instead of 8,676. The correct key is response_type, and it needs js_render: true beside it. Unknown keys are ignored rather than rejected here, so check the size of what came back when a format setting appears to have no effect.

Q: Can one Action expose more than one Scrapeless capability?

Yes — add a path and an operationId per operation in the same document. Keep each one narrow and keep the enum constraints, since a single operation with a free-text actor field invites the model to guess. Least privilege also makes the Action easier to review later.

Q: Does this work in a normal ChatGPT conversation or only in a custom GPT?

Actions belong to a GPT you configure, so the capability lives in that GPT rather than in every conversation. Anyone you share it with gets the operation; whether they supply their own key depends on how you set the authentication up.

At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.

Most Popular Articles

Catalogue