{
  "openapi": "3.1.0",
  "info": {
    "title": "AgentSearch Web Extract",
    "version": "0.2.1",
    "description": "Pocket Network service agentsearch-web-extract-v1 (REST). Web page to clean Markdown for AI agents, LLM and RAG pipelines. Every response, success or error, is a JSON object. No caller authentication; all inputs arrive in the JSON request body. POST / is accepted as an alias of POST /v1/extract. Target-site failures (HTTP 4xx/5xx from the target, timeout, DNS or connection failure, unsupported content type, too many redirects) are NOT service errors: they return HTTP 200 with the usual response fields (request_id, url, meta.fetched_at), empty markdown/text and a non-null `error` object (e.g. code TARGET_HTTP_ERROR with upstream_status 404). The whole target fetch (including one retry) has a hard 4-second deadline (TARGET_TIMEOUT), so answers arrive in about 4 s at worst. Pages are fetched with a descriptive bot User-Agent (AgentSearchExtract/0.2, +https://agentsearchhq.com); on a target 401/403 one retry is made with a browser User-Agent if time remains. Some sites (e.g. paywalls) still refuse automated access and return TARGET_HTTP_ERROR. Invalid or blocked input returns 400; 413 for oversized bodies; 429 when saturated; HTTP 5xx is reserved for genuine internal failures."
  },
  "servers": [
    {
      "url": "https://pocket-extract.agentsearchhq.com",
      "description": "Reference backend (direct)"
    }
  ],
  "paths": {
    "/v1/extract": {
      "post": {
        "operationId": "extract",
        "summary": "Extract clean markdown/text, title, links and metadata from one URL",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequest"
              },
              "example": {
                "url": "https://example.com/",
                "formats": [
                  "markdown"
                ],
                "max_chars": 2000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction result. If the target page could not be fetched, `error` is a TargetError object and markdown/text are empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                },
                "examples": {
                  "success": {
                    "value": {
                      "request_id": "a1b2",
                      "url": "https://example.com/",
                      "title": "Example Domain",
                      "markdown": "# Example Domain ...",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": 200,
                        "content_type": "text/html",
                        "fetched_at": "2026-09-25T12:00:00+00:00",
                        "chars": 167,
                        "truncated": false
                      },
                      "error": null
                    }
                  },
                  "target_error": {
                    "value": {
                      "request_id": "c3d4",
                      "url": "https://en.wikipedia.org/wiki/No_such_page_example",
                      "requested_url": "https://en.wikipedia.org/wiki/No_such_page_example",
                      "title": null,
                      "markdown": "",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": 404,
                        "content_type": "text/html",
                        "fetched_at": "2026-09-25T12:00:00+00:00",
                        "chars": 0,
                        "truncated": false
                      },
                      "error": {
                        "code": "TARGET_HTTP_ERROR",
                        "message": "Target site returned HTTP 404.",
                        "request_id": "c3d4",
                        "upstream_status": 404,
                        "retryable": false
                      }
                    }
                  },
                  "target_timeout": {
                    "value": {
                      "request_id": "e5f6",
                      "url": "https://slow.example.net/",
                      "requested_url": "https://slow.example.net/",
                      "title": null,
                      "markdown": "",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": null,
                        "content_type": null,
                        "fetched_at": "2026-09-25T12:00:04+00:00",
                        "chars": 0,
                        "truncated": false
                      },
                      "error": {
                        "code": "TARGET_TIMEOUT",
                        "message": "Target site did not respond within 4 s.",
                        "request_id": "e5f6",
                        "upstream_status": null,
                        "retryable": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or blocked URL (INVALID_REQUEST, SSRF_BLOCKED; JSON error object)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Service saturated after a brief queue wait (CAPACITY_LIMIT) or per-application rate limit (APPLICATION_RATE_LIMITED). Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Genuine internal failure (INTERNAL_ERROR). Never used for target-site problems.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body too large (REQUEST_TOO_LARGE; JSON error object)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/": {
      "post": {
        "operationId": "extractRoot",
        "summary": "Alias of POST /v1/extract (for relays that arrive without a path)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequest"
              },
              "example": {
                "url": "https://example.com/",
                "formats": [
                  "markdown"
                ],
                "max_chars": 2000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction result. If the target page could not be fetched, `error` is a TargetError object and markdown/text are empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                },
                "examples": {
                  "success": {
                    "value": {
                      "request_id": "a1b2",
                      "url": "https://example.com/",
                      "title": "Example Domain",
                      "markdown": "# Example Domain ...",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": 200,
                        "content_type": "text/html",
                        "fetched_at": "2026-09-25T12:00:00+00:00",
                        "chars": 167,
                        "truncated": false
                      },
                      "error": null
                    }
                  },
                  "target_error": {
                    "value": {
                      "request_id": "c3d4",
                      "url": "https://en.wikipedia.org/wiki/No_such_page_example",
                      "requested_url": "https://en.wikipedia.org/wiki/No_such_page_example",
                      "title": null,
                      "markdown": "",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": 404,
                        "content_type": "text/html",
                        "fetched_at": "2026-09-25T12:00:00+00:00",
                        "chars": 0,
                        "truncated": false
                      },
                      "error": {
                        "code": "TARGET_HTTP_ERROR",
                        "message": "Target site returned HTTP 404.",
                        "request_id": "c3d4",
                        "upstream_status": 404,
                        "retryable": false
                      }
                    }
                  },
                  "target_timeout": {
                    "value": {
                      "request_id": "e5f6",
                      "url": "https://slow.example.net/",
                      "requested_url": "https://slow.example.net/",
                      "title": null,
                      "markdown": "",
                      "text": "",
                      "links": [],
                      "meta": {
                        "status_code": null,
                        "content_type": null,
                        "fetched_at": "2026-09-25T12:00:04+00:00",
                        "chars": 0,
                        "truncated": false
                      },
                      "error": {
                        "code": "TARGET_TIMEOUT",
                        "message": "Target site did not respond within 4 s.",
                        "request_id": "e5f6",
                        "upstream_status": null,
                        "retryable": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or blocked URL (INVALID_REQUEST, SSRF_BLOCKED; JSON error object)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Service saturated after a brief queue wait (CAPACITY_LIMIT) or per-application rate limit (APPLICATION_RATE_LIMITED). Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Genuine internal failure (INTERNAL_ERROR). Never used for target-site problems.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body too large (REQUEST_TOO_LARGE; JSON error object)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/version": {
      "get": {
        "operationId": "version",
        "summary": "Identity probe",
        "responses": {
          "200": {
            "description": "Service identity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "service": {
                      "type": "string",
                      "const": "agentsearch-web-extract-v1"
                    },
                    "version": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "service",
                    "version"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "Readiness probe",
        "responses": {
          "200": {
            "description": "Ready",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    }
                  },
                  "required": [
                    "status"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/capabilities": {
      "get": {
        "operationId": "capabilities",
        "summary": "Routes and limits",
        "responses": {
          "200": {
            "description": "Capabilities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ExtractRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute http(s) URL of the page to extract."
          },
          "formats": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "markdown",
                "text"
              ]
            },
            "description": "Output formats to populate. Default [\"markdown\"]."
          },
          "max_chars": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000,
            "default": 50000,
            "description": "Cap on extracted characters."
          },
          "include_links": {
            "type": "boolean",
            "default": true,
            "description": "Include up to 100 absolute links found on the page."
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 60000,
            "default": 4000,
            "description": "Target fetch deadline in ms; values above 4000 are capped at 4000 (hard 4 s deadline)."
          }
        },
        "additionalProperties": false
      },
      "ExtractResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "markdown": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "href": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "status_code": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Target HTTP status (null if no response was received)."
              },
              "content_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "fetched_at": {
                "type": "string",
                "format": "date-time"
              },
              "chars": {
                "type": "integer"
              },
              "truncated": {
                "type": "boolean"
              }
            }
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TargetError"
              }
            ],
            "description": "null on success; TargetError when the target page could not be fetched/extracted (response is still HTTP 200)."
          },
          "requested_url": {
            "type": "string",
            "description": "The URL as requested (present on target errors; `url` is the final URL after redirects)."
          }
        },
        "required": [
          "request_id",
          "url",
          "meta",
          "error"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "INVALID_REQUEST",
                  "SSRF_BLOCKED",
                  "REQUEST_TOO_LARGE",
                  "CAPACITY_LIMIT",
                  "APPLICATION_RATE_LIMITED",
                  "INTERNAL_ERROR"
                ],
                "description": "400 INVALID_REQUEST (bad JSON, url or formats), 400 SSRF_BLOCKED (private/loopback/metadata address), 413 REQUEST_TOO_LARGE, 429 CAPACITY_LIMIT / APPLICATION_RATE_LIMITED, 500 INTERNAL_ERROR."
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        }
      },
      "TargetError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "TARGET_HTTP_ERROR",
              "TARGET_TIMEOUT",
              "TARGET_DNS_ERROR",
              "TARGET_CONNECT_ERROR",
              "TARGET_FETCH_FAILED",
              "TARGET_TOO_MANY_REDIRECTS",
              "UNSUPPORTED_CONTENT"
            ],
            "description": "TARGET_HTTP_ERROR: target answered 4xx/5xx (see upstream_status). TARGET_TIMEOUT: no complete answer within the 4 s deadline. TARGET_DNS_ERROR / TARGET_CONNECT_ERROR: host not resolvable or not reachable. TARGET_TOO_MANY_REDIRECTS: redirect loop or more than the allowed hops. TARGET_FETCH_FAILED: other transport failure. UNSUPPORTED_CONTENT: not HTML or text (e.g. PDF, image)."
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "upstream_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status returned by the target site, if any."
          },
          "retryable": {
            "type": "boolean",
            "description": "True for timeouts, connection failures and target 5xx/429."
          }
        }
      }
    }
  }
}
