searlo-tech Web Data API

Retrieval. Search finds the pages; these read them — scrape one, map a site, crawl it, or extract structured records. A fetched page costs 1 credit; from 2026-11-01, a page that had to be rendered (render true, actions, or the automatic fallback) costs 5, and its `via` says `browser`. /map is unchanged. The automatic fallback is for accounts that have bought credits.

Operations 10

POST /scrape Scrape one page #
POST /map List a site's URLs #
POST /scrape/batch Batch scrape #
POST /crawl Start a crawl #
GET /crawl List crawls #
GET /crawl/{jobId} Crawl status #
DELETE /crawl/{jobId} Cancel a crawl #
GET /crawl/{jobId}/results Crawl results #
POST /extract Extract structured data #
POST /search-and-scrape Search and scrape in one call #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/searlo-tech:searlo-tech-web-data-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

searlo-tech-web-data-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Searlo Web Data API
  description: Live search results as clean JSON. The search endpoints return Google results — web, images, news, video, shopping, maps, scholar, patents and the full results page with ads, People Also Ask and AI Overviews — plus AI answers (ChatGPT, Copilot, Gemini), rank checks, web data (scrape, crawl, map, extract) and account endpoints. Get a free API key with 3,000 credits (no card required) at https://dashboard.searlo.tech. Not affiliated with or endorsed by Google LLC or any other search engine. All trademarks belong to their owners.
  version: 1.2.0
  contact:
    name: Searlo
    url: https://searlo.tech
    email: hello@searlo.tech
  license:
    name: Proprietary API — see https://searlo.tech/terms
servers:
- url: https://api.searlo.tech/api/v1
  description: Production
security:
- ApiKeyAuth: []
- BearerAuth: []
tags:
- name: Web Data
  description: Retrieval. Search finds the pages; these read them — scrape one, map a site, crawl it, or extract structured records. A fetched page costs 1 credit; from 2026-11-01, a page that had to be rendered (render true, actions, or the automatic fallback) costs 5, and its `via` says `browser`. /map is unchanged. The automatic fallback is for accounts that have bought credits.
paths:
  /scrape:
    post:
      tags:
      - Web Data
      summary: Scrape one page
      description: 'Fetch a URL and return every requested view of it in one call: markdown, plain text, raw html, links, images, headings, tables and the page''s own JSON-LD, plus title, description, author, published date, canonical URL and Open Graph. Pages that render client-side are rendered automatically (on accounts that have bought credits). 1 credit; 2 when maxAge or cache false forces a live fetch; from 2026-11-01, 4 more when the page had to be rendered (`via: browser`). The url may be an internationalised domain name (https://www.löschwagen.com), fetched, cached and billed under its encoded form; a malformed URL or a control character in it is a 400, never charged.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  maxLength: 2048
                formats:
                  type: array
                  items:
                    type: string
                    enum:
                    - markdown
                    - text
                    - html
                    - links
                    - images
                    - headings
                    - tables
                    - json_ld
                render:
                  type: boolean
                  description: Force a browser render (true) or never render (false). From 2026-11-01 a page fetched in a browser costs 4 credits more.
                onlyMainContent:
                  type: boolean
                  default: true
                chunk:
                  type: boolean
                  description: Split content into overlapping chunks
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '422':
          $ref: '#/components/responses/TargetError'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postScrape
      x-operation-id-source: derived
  /map:
    post:
      tags:
      - Web Data
      summary: List a site's URLs
      description: What exists on a site, before paying to crawl it. Reads robots.txt for declared sitemaps, walks them (including sitemap indexes), and falls back to the homepage's internal links when a site publishes no sitemap. The url may be an internationalised domain name.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  maxLength: 2048
                limit:
                  type: integer
                  minimum: 1
                  maximum: 5000
                  default: 1000
                search:
                  type: string
                  description: Only URLs containing this text
                includeSubdomains:
                  type: boolean
                sitemapOnly:
                  type: boolean
                  description: Sitemaps only, no link harvesting.
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '422':
          $ref: '#/components/responses/TargetError'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postMap
      x-operation-id-source: derived
  /scrape/batch:
    post:
      tags:
      - Web Data
      summary: Batch scrape
      description: 'Many URLs, one job. Returns a job id immediately; poll it or supply a webhook. URLs are normalised and de-duplicated before queueing, so the same page written two ways is one fetch and one credit — compare `submitted` with `urlCount` in the response. Nothing is discovered: traversal options are ignored, because you already named every URL. Every page is fetched live at 1 credit (5 from 2026-11-01 for a page that needed a browser): `maxAge`, `cache`, `includeLinks` and `excludeSelectors` are accepted but have no effect on a batch, and only markdown, text, html, links and json_ld are stored. Status, results, cancellation and billing all use the crawl endpoints.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - urls
              properties:
                urls:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items:
                    type: string
                    maxLength: 2048
                formats:
                  type: array
                  items:
                    type: string
                    enum:
                    - markdown
                    - text
                    - html
                    - links
                    - json_ld
                render:
                  type: boolean
                onlyMainContent:
                  type: boolean
                  default: true
                concurrency:
                  type: integer
                  minimum: 1
                  maximum: 8
                storeContent:
                  type: boolean
                  default: true
                maxAge:
                  type: integer
                  minimum: 0
                  maximum: 2592000
                  description: 'Accepted for parity with /scrape but not used by a batch: every page is fetched live.

                    '
                webhook:
                  type: object
                  description: Called once when the job ends; not retried.
                  properties:
                    url:
                      type: string
                    secret:
                      type: string
      responses:
        '202':
          description: Batch queued; the body carries the job id, urlCount and its status/results URLs.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postScrapeBatch
      x-operation-id-source: derived
  /crawl:
    post:
      tags:
      - Web Data
      summary: Start a crawl
      description: 'Queue a crawl and get a job id back immediately. Credits are charged per page as it runs, so a job with `limit: 500` on a twelve-page site costs twelve; from 2026-11-01, a page that needed a browser costs 5 instead of 1. Poll the job or supply a webhook. robots.txt is obeyed by default.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  maxLength: 2048
                maxDepth:
                  type: integer
                  minimum: 0
                  maximum: 10
                  default: 2
                limit:
                  type: integer
                  minimum: 1
                  maximum: 5000
                  default: 100
                includePaths:
                  type: array
                  items:
                    type: string
                  description: Globs, e.g. /blog/*
                excludePaths:
                  type: array
                  items:
                    type: string
                formats:
                  type: array
                  items:
                    type: string
                    enum:
                    - markdown
                    - text
                    - html
                    - links
                    - json_ld
                allowSubdomains:
                  type: boolean
                respectRobots:
                  type: boolean
                  default: true
                concurrency:
                  type: integer
                  minimum: 1
                  maximum: 8
                  default: 3
                webhook:
                  type: object
                  properties:
                    url:
                      type: string
                    secret:
                      type: string
      responses:
        '202':
          description: Crawl queued; the body carries the job id and its status/results URLs
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postCrawl
      x-operation-id-source: derived
    get:
      tags:
      - Web Data
      summary: List crawls
      description: Your crawls, newest first. Free.
      parameters:
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
      - name: status
        in: query
        schema:
          type: string
          enum:
          - queued
          - running
          - completed
          - failed
          - cancelled
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '401':
          $ref: '#/components/responses/Unauthorized'
      operationId: getCrawl
      x-operation-id-source: derived
  /crawl/{jobId}:
    get:
      tags:
      - Web Data
      summary: Crawl status
      description: Progress, credits charged so far, and why the crawl stopped. Free.
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Crawl not found
      operationId: getCrawlByJobId
      x-operation-id-source: derived
    delete:
      tags:
      - Web Data
      summary: Cancel a crawl
      description: Stop spending on a crawl. Pages already fetched stay readable. Free.
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Crawl not found
      operationId: deleteCrawlByJobId
      x-operation-id-source: derived
  /crawl/{jobId}/results:
    get:
      tags:
      - Web Data
      summary: Crawl results
      description: The crawled pages, paginated by cursor. Free — the pages were billed as they were fetched.
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
      - name: cursor
        in: query
        schema:
          type: string
      - name: status
        in: query
        schema:
          type: string
          enum:
          - completed
          - failed
          - skipped
          - queued
          - all
          default: completed
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Crawl not found
      operationId: getCrawlByJobIdResults
      x-operation-id-source: derived
  /extract:
    post:
      tags:
      - Web Data
      summary: Extract structured data
      description: 'URLs plus a field schema, records out. Values come first from what the page itself declares (schema.org JSON-LD, Open Graph, meta tags), then from unambiguous patterns in the text. Every field reports its source, and a field that cannot be found comes back null and is listed in `missingFields` rather than guessed. The URLs (internationalised domain names included) are fetched several at a time inside one overall time budget (75 seconds by default): pages that arrive in time are served and billed, the rest come back as rows that say why (`errorCode`, `retryable`), and `partial` is true when the budget ran out. Billing is one credit per page actually fetched (from 2026-11-01, five for a page that had to be rendered); a failed row is free.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - schema
              properties:
                urls:
                  type: array
                  items:
                    type: string
                  maxItems: 20
                url:
                  type: string
                schema:
                  type: object
                  description: 'Field name to type: string, number, integer, float, boolean or array'
                  additionalProperties:
                    type: string
                    enum:
                    - string
                    - number
                    - integer
                    - float
                    - boolean
                    - array
                useLlm:
                  type: boolean
                  default: true
                  description: 'Fall back to a language model for fields the page''s own JSON-LD, Open Graph and meta tags did not declare. Defaults to true when an LLM is configured on the deployment; has no effect otherwise. Set false to force the deterministic pass only.

                    '
                onlyMainContent:
                  type: boolean
                  default: true
                  description: Strip navigation, headers and footers from the page before extracting.
      responses:
        '200':
          description: One row per URL, in the order sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '422':
          $ref: '#/components/responses/TargetError'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postExtract
      x-operation-id-source: derived
  /search-and-scrape:
    post:
      tags:
      - Web Data
      summary: Search and scrape in one call
      description: 'Run a web search and fetch the results'' pages. Returns the results with their content attached; a page that cannot be fetched comes back with `scraped: false` beside the ones that worked. One credit for the search plus one per page actually scraped; from 2026-11-01, five for a page that needed a browser.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - q
              properties:
                q:
                  type: string
                  maxLength: 500
                limit:
                  type: integer
                  minimum: 1
                  maximum: 20
                  default: 5
                scrape:
                  type: boolean
                  default: true
                formats:
                  type: array
                  items:
                    type: string
                    enum:
                    - markdown
                    - text
                    - html
                    - json_ld
                gl:
                  type: string
                  maxLength: 5
                hl:
                  type: string
                  maxLength: 5
      responses:
        '200':
          $ref: '#/components/responses/SearchResults'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '429':
          $ref: '#/components/responses/RateLimited'
      operationId: postSearchAndScrape
      x-operation-id-source: derived
components:
  responses:
    RateLimited:
      description: 'The account''s rate limit (code 4201), the API key''s own (code 4202) or the cap on searches in flight (code 4203, errorCode CONCURRENCY_LIMIT, Retry-After 1, X-Concurrency-Limit carries the cap). Wait Retry-After seconds. See also AT_CAPACITY / BUSY / VERIFIER_BUSY, a full lane rather than a limit reached. Never charged.

        '
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing, invalid or expired API key (code UNAUTHORIZED)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    SearchResults:
      description: Search results
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SearchResponse'
    BadRequest:
      description: 'Invalid request (code VALIDATION_ERROR; errors lists the fields). Search endpoints also answer 400 PAGE_OUT_OF_RANGE (maxPage) and DATE_FILTER_UNAVAILABLE, with the name in errorCode. Never charged.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InsufficientCredits:
      description: 'Account balance too low to complete the request (code INSUFFICIENT_CREDITS). details gives required, available and shortfall; topUpUrl links to buying more.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TargetError:
      description: 'The site could not be fetched; the name is in errorCode. TARGET_REFUSED: the site refused every attempt or answered with an error of its own (targetStatus is its own status; retryable only for its 429 or 5xx). TARGET_NOT_ALLOWED: a private or reserved network address (retryable false). TARGET_UNREACHABLE: no attempt could fetch it, for example its secure-connection handshake is refused, it redirects in a loop or the page is too large (retryable false; targetError says which). Not charged.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    SearchResponse:
      type: object
      description: The answer is the body itself (no success/data envelope).
      properties:
        page:
          type: integer
        nextPage:
          type:
          - integer
          - 'null'
          description: The next page, or null when the results end here.
        totalResults:
          type: integer
        credits:
          type: number
          description: Credits charged for this request; 0 for an answer with no results. Equals the X-Credits-Deducted header.
        searchParameters:
          type: object
          properties:
            q:
              type: string
            type:
              type: string
            gl:
              type: string
            hl:
              type: string
            num:
              type: integer
        organic:
          type: array
          items:
            type: object
            properties:
              position:
                type: integer
              title:
                type: string
              link:
                type: string
              snippet:
                type: string
              domain:
                type: string
              displayedLink:
                type: string
              favicon:
                type: string
              date:
                type: string
              thumbnail:
                type: string
              breadcrumb:
                type: string
    ExtractResponse:
      type: object
      properties:
        searchParameters:
          type: object
          properties:
            type:
              type: string
              example: extract
            urls:
              type: integer
            fields:
              type: array
              items:
                type: string
        mode:
          type: string
          enum:
          - structured
          - structured+llm
        llmAvailable:
          type: boolean
        totalResults:
          type: integer
          description: Rows in data.
        partial:
          type: boolean
          description: 'Present and true when the time budget ran out before every URL was fetched: the rows say which (TIMEOUT, NOT_FETCHED); the rest are served and billed.'
        data:
          type: array
          items:
            type: object
            required:
            - url
            - success
            - data
            properties:
              url:
                type: string
              success:
                type: boolean
                description: 'true: the page was fetched (1 credit). false: free.'
              data:
                type:
                - object
                - 'null'
                additionalProperties: true
                description: The record; null on a failed row.
              _sources:
                type: object
                additionalProperties:
                  type: string
                  enum:
                  - json-ld
                  - meta
                  - text
                  - llm
              missingFields:
                type: array
                items:
                  type: string
              via:
                type:
                - string
                - 'null'
              llmUsed:
                type: boolean
              llmSkipped:
                type: string
                enum:
                - time budget
                description: The page came in too late to ask the model for its missing fields.
              errorCode:
                type: string
                enum:
                - TARGET_REFUSED
                - TARGET_NOT_ALLOWED
                - TARGET_UNREACHABLE
                - INVALID_REQUEST
                - SCRAPE_FAILED
                - TIMEOUT
                - NOT_FETCHED
                description: On a failed row.
              error:
                type: string
                description: 'On a failed row: what happened.'
              retryable:
                type: boolean
                description: 'On a failed row: whether a retry may succeed.'
              targetStatus:
                type: integer
                description: 'On a TARGET_REFUSED row: the site''s own HTTP status.'
        source:
          type: string
        credits:
          type: number
          description: Pages fetched.
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        code:
          description: 'Stable machine-readable name of the failure. Strings: VALIDATION_ERROR, INVALID_JSON, INVALID_IDEMPOTENCY_KEY, CITY_LOCATION_UNAVAILABLE, UNKNOWN_LOCATION, INVALID_DOMAIN, UNAUTHORIZED, INSUFFICIENT_CREDITS, FORBIDDEN, FEATURE_NOT_AVAILABLE, API_KEY_SCOPE_FORBIDDEN, NOT_FOUND, IDEMPOTENCY_REQUEST_IN_PROGRESS, PAYLOAD_TOO_LARGE, QUERY_NOT_SERVED, COUNTRY_UNAVAILABLE, IDEMPOTENCY_KEY_REUSED, TARGET_NOT_ALLOWED, AT_CAPACITY, BUSY, VERIFIER_BUSY, INTERNAL_ERROR, UPSTREAM_UNAVAILABLE, UPSTREAM_RATE_LIMITED, WARMING, UPSTREAM_TIMEOUT, CREDIT_SYSTEM_UNAVAILABLE, REVIEWS_WITHHELD, SERVICE_UNAVAILABLE, UPSTREAM_FAILED. The rate-limit 429s use numbers: 4201 (account), 4202 (API key), 4203 (searches in flight). Some 4xx carry their name in errorCode instead (see errorCode). No error is charged.

            '
          oneOf:
          - type: string
          - type: integer
          example: CITY_LOCATION_UNAVAILABLE
        message:
          type: string
          description: For people; may be reworded. Branch on code.
        retryable:
          type: boolean
          description: Whether the same request can succeed later.
        retryAfterSec:
          type: integer
          description: Seconds to wait before a retry (also the Retry-After header).
        errors:
          type: array
          description: Validation errors, one per field.
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
        suggestions:
          type: array
          items:
            type: string
          description: 'UNKNOWN_LOCATION: close matches.'
        location:
          type: string
          description: 'CITY_LOCATION_UNAVAILABLE: the place the request named.'
        errorCode:
          type: string
          description: 'The name on errors that carry it here instead of in code, so read code and fall back to errorCode: PAGE_OUT_OF_RANGE (with maxPage), DATE_FILTER_UNAVAILABLE, INVALID_REQUEST, PLACE_NOT_FOUND, TARGET_REFUSED (with targetStatus), TARGET_UNREACHABLE (with targetError), INSUFFICIENT_CREDITS on POST /extract. Also set with code on 4203 (CONCURRENCY_LIMIT), FEATURE_NOT_AVAILABLE, TARGET_NOT_ALLOWED, REVIEWS_WITHHELD and every 503.

            '
        retryAfter:
          type: integer
          description: Seconds to wait, on 429 4201 to 4203 and on a retryable 503.
        maxPage:
          type: integer
          description: 'PAGE_OUT_OF_RANGE: the last page that can be served at this limit.'
        targetStatus:
          type: integer
          description: 'TARGET_REFUSED: the site''s own HTTP status.'
        targetError:
          type: string
          description: 'TARGET_UNREACHABLE: why the site could not be fetched.'
        details:
          type: object
          description: 'INSUFFICIENT_CREDITS: required, available and shortfall.'
          additionalProperties:
            type: number
        topUpUrl:
          type: string
          description: 'INSUFFICIENT_CREDITS: a link to buy more credits.'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: 'API key from your Searlo dashboard, e.g. sk_.... Get a free key with 3,000 credits at https://dashboard.searlo.tech (no card required).

        '
    BearerAuth:
      type: http
      scheme: bearer
      description: 'The same API key, sent as Authorization: Bearer sk_.... A bearer token that does not start with sk_ is read as a dashboard login token.

        '
    CseKeyParam:
      type: apiKey
      in: query
      name: key
      description: 'The Searlo API key as the key query parameter, where Google''s Custom Search JSON API takes its key. Used by /customsearch/v1 only.

        '
externalDocs:
  description: Full documentation
  url: https://searlo.tech/docs