diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 4dedeae..511dd51 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.5.0"
+ ".": "2.6.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 0ac1eb3..33b7080 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 32
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-242450ea46eb8c3e843fd6c4bf87e73192b5f62f6da697cd091d13c6aa7a991b.yml
-openapi_spec_hash: c1c561976de1abcacede71fd5ab9b3d9
-config_hash: 70e7e80b5e87f94981bee396c6cd41e8
+configured_endpoints: 37
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-960cb623c7ec84bf4dc0f5945cbc19eec9cca48271071f400d96066eaa55dbd6.yml
+openapi_spec_hash: 84fd39e3f4dc964bf0c32d4e95da1b34
+config_hash: 2bea1743c84d63bd61f8501a6ea63065
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4444631..5cebd96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 2.6.0 (2026-07-31)
+
+Full Changelog: [v2.5.0...v2.6.0](https://github.com/context-dot-dev/context-python-sdk/compare/v2.5.0...v2.6.0)
+
+### Features
+
+* **api:** api update ([6d99f45](https://github.com/context-dot-dev/context-python-sdk/commit/6d99f453a62eb95134599a6e1d9c648788dfe3b7))
+* **api:** api update ([ff409ed](https://github.com/context-dot-dev/context-python-sdk/commit/ff409edadd0a514fa22948f8abd741b9676ca1b6))
+* **api:** api update ([925ed2c](https://github.com/context-dot-dev/context-python-sdk/commit/925ed2c6ec48e165f9807101bbbfdbda9c668709))
+* **api:** api update ([3f3a903](https://github.com/context-dot-dev/context-python-sdk/commit/3f3a90368fe919e2d727f484db792ca2d631f3bf))
+* **api:** api update ([226b29c](https://github.com/context-dot-dev/context-python-sdk/commit/226b29c8e73b0c785ad9c2692225cfac44365395))
+* **api:** manual updates ([f32255d](https://github.com/context-dot-dev/context-python-sdk/commit/f32255d61d0e43804dacbfc7b578bd909899db61))
+
## 2.5.0 (2026-07-22)
Full Changelog: [v2.4.0...v2.5.0](https://github.com/context-dot-dev/context-python-sdk/compare/v2.4.0...v2.5.0)
diff --git a/api.md b/api.md
index fe5b201..d95bf72 100644
--- a/api.md
+++ b/api.md
@@ -133,3 +133,27 @@ Methods:
- client.monitors.list_runs(monitor_id, \*\*params) -> MonitorListRunsResponse
- client.monitors.retrieve_change(change_id) -> MonitorRetrieveChangeResponse
- client.monitors.run(monitor_id) -> MonitorRunResponse
+
+# Batch
+
+Types:
+
+```python
+from context.dev.types import (
+ ErrorCount,
+ Error,
+ BatchRetrieveResponse,
+ BatchListResponse,
+ BatchCancelResponse,
+ BatchGetResultsResponse,
+ BatchSubmitResponse,
+)
+```
+
+Methods:
+
+- client.batch.retrieve(batch_id) -> BatchRetrieveResponse
+- client.batch.list(\*\*params) -> BatchListResponse
+- client.batch.cancel(batch_id) -> BatchCancelResponse
+- client.batch.get_results(batch_id, \*\*params) -> BatchGetResultsResponse
+- client.batch.submit(\*\*params) -> BatchSubmitResponse
diff --git a/pyproject.toml b/pyproject.toml
index 32eb11e..7493050 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "context.dev"
-version = "2.5.0"
+version = "2.6.0"
description = "The official Python library for the context.dev API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/context/dev/_client.py b/src/context/dev/_client.py
index 800374f..cf4d545 100644
--- a/src/context/dev/_client.py
+++ b/src/context/dev/_client.py
@@ -35,9 +35,10 @@
)
if TYPE_CHECKING:
- from .resources import ai, web, brand, parse, utility, industry, monitors
+ from .resources import ai, web, batch, brand, parse, utility, industry, monitors
from .resources.ai import AIResource, AsyncAIResource
from .resources.web import WebResource, AsyncWebResource
+ from .resources.batch import BatchResource, AsyncBatchResource
from .resources.brand import BrandResource, AsyncBrandResource
from .resources.parse import ParseResource, AsyncParseResource
from .resources.utility import UtilityResource, AsyncUtilityResource
@@ -165,6 +166,12 @@ def monitors(self) -> MonitorsResource:
return MonitorsResource(self)
+ @cached_property
+ def batch(self) -> BatchResource:
+ from .resources.batch import BatchResource
+
+ return BatchResource(self)
+
@cached_property
def with_raw_response(self) -> ContextDevWithRawResponse:
return ContextDevWithRawResponse(self)
@@ -387,6 +394,12 @@ def monitors(self) -> AsyncMonitorsResource:
return AsyncMonitorsResource(self)
+ @cached_property
+ def batch(self) -> AsyncBatchResource:
+ from .resources.batch import AsyncBatchResource
+
+ return AsyncBatchResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncContextDevWithRawResponse:
return AsyncContextDevWithRawResponse(self)
@@ -551,6 +564,12 @@ def monitors(self) -> monitors.MonitorsResourceWithRawResponse:
return MonitorsResourceWithRawResponse(self._client.monitors)
+ @cached_property
+ def batch(self) -> batch.BatchResourceWithRawResponse:
+ from .resources.batch import BatchResourceWithRawResponse
+
+ return BatchResourceWithRawResponse(self._client.batch)
+
class AsyncContextDevWithRawResponse:
_client: AsyncContextDev
@@ -603,6 +622,12 @@ def monitors(self) -> monitors.AsyncMonitorsResourceWithRawResponse:
return AsyncMonitorsResourceWithRawResponse(self._client.monitors)
+ @cached_property
+ def batch(self) -> batch.AsyncBatchResourceWithRawResponse:
+ from .resources.batch import AsyncBatchResourceWithRawResponse
+
+ return AsyncBatchResourceWithRawResponse(self._client.batch)
+
class ContextDevWithStreamedResponse:
_client: ContextDev
@@ -655,6 +680,12 @@ def monitors(self) -> monitors.MonitorsResourceWithStreamingResponse:
return MonitorsResourceWithStreamingResponse(self._client.monitors)
+ @cached_property
+ def batch(self) -> batch.BatchResourceWithStreamingResponse:
+ from .resources.batch import BatchResourceWithStreamingResponse
+
+ return BatchResourceWithStreamingResponse(self._client.batch)
+
class AsyncContextDevWithStreamedResponse:
_client: AsyncContextDev
@@ -707,6 +738,12 @@ def monitors(self) -> monitors.AsyncMonitorsResourceWithStreamingResponse:
return AsyncMonitorsResourceWithStreamingResponse(self._client.monitors)
+ @cached_property
+ def batch(self) -> batch.AsyncBatchResourceWithStreamingResponse:
+ from .resources.batch import AsyncBatchResourceWithStreamingResponse
+
+ return AsyncBatchResourceWithStreamingResponse(self._client.batch)
+
Client = ContextDev
diff --git a/src/context/dev/_version.py b/src/context/dev/_version.py
index 76defcd..de35f16 100644
--- a/src/context/dev/_version.py
+++ b/src/context/dev/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "context.dev"
-__version__ = "2.5.0" # x-release-please-version
+__version__ = "2.6.0" # x-release-please-version
diff --git a/src/context/dev/resources/__init__.py b/src/context/dev/resources/__init__.py
index 12b45f9..c99c0f9 100644
--- a/src/context/dev/resources/__init__.py
+++ b/src/context/dev/resources/__init__.py
@@ -16,6 +16,14 @@
WebResourceWithStreamingResponse,
AsyncWebResourceWithStreamingResponse,
)
+from .batch import (
+ BatchResource,
+ AsyncBatchResource,
+ BatchResourceWithRawResponse,
+ AsyncBatchResourceWithRawResponse,
+ BatchResourceWithStreamingResponse,
+ AsyncBatchResourceWithStreamingResponse,
+)
from .brand import (
BrandResource,
AsyncBrandResource,
@@ -100,4 +108,10 @@
"AsyncMonitorsResourceWithRawResponse",
"MonitorsResourceWithStreamingResponse",
"AsyncMonitorsResourceWithStreamingResponse",
+ "BatchResource",
+ "AsyncBatchResource",
+ "BatchResourceWithRawResponse",
+ "AsyncBatchResourceWithRawResponse",
+ "BatchResourceWithStreamingResponse",
+ "AsyncBatchResourceWithStreamingResponse",
]
diff --git a/src/context/dev/resources/batch.py b/src/context/dev/resources/batch.py
new file mode 100644
index 0000000..50e9ded
--- /dev/null
+++ b/src/context/dev/resources/batch.py
@@ -0,0 +1,641 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import batch_list_params, batch_submit_params, batch_get_results_params
+from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.batch_list_response import BatchListResponse
+from ..types.batch_cancel_response import BatchCancelResponse
+from ..types.batch_submit_response import BatchSubmitResponse
+from ..types.batch_retrieve_response import BatchRetrieveResponse
+from ..types.batch_get_results_response import BatchGetResultsResponse
+
+__all__ = ["BatchResource", "AsyncBatchResource"]
+
+
+class BatchResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> BatchResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/context-dot-dev/context-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return BatchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BatchResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/context-dot-dev/context-python-sdk#with_streaming_response
+ """
+ return BatchResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ batch_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchRetrieveResponse:
+ """Check progress and get download links when the batch finishes.
+
+ Also returns the
+ rejected-URL list and webhook signing secret from submission, so nothing is lost
+ if the submit response was dropped.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return self._get(
+ path_template("/batch/{batch_id}", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ q: str | Omit = omit,
+ search_type: Literal["exact", "prefix"] | Omit = omit,
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] | Omit = omit,
+ tags: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchListResponse:
+ """List your batches from newest to oldest.
+
+ Filter by status or continue with a
+ cursor.
+
+ Args:
+ cursor: Cursor from the previous page.
+
+ limit: Batches per page. Defaults to 25.
+
+ q: Free-text search term, matched against the batch id, crawl source (start URL or
+ sitemap domain), and tags.
+
+ search_type: `prefix` for as-you-type prefix matching (default), `exact` for full-token
+ matching.
+
+ status: Filter by status.
+
+ tags: Comma-separated list of tags to filter by (matches batches having any of them).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/batch/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "q": q,
+ "search_type": search_type,
+ "status": status,
+ "tags": tags,
+ },
+ batch_list_params.BatchListParams,
+ ),
+ ),
+ cast_to=BatchListResponse,
+ )
+
+ def cancel(
+ self,
+ batch_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchCancelResponse:
+ """Stop a batch from starting new pages.
+
+ In-progress pages finish, and unused
+ credits are refunded.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return self._post(
+ path_template("/batch/{batch_id}/cancel", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchCancelResponse,
+ )
+
+ def get_results(
+ self,
+ batch_id: str,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchGetResultsResponse:
+ """
+ Page through the result records of a finished batch as JSON, in the same order
+ as the downloadable result files. Use this instead of downloading and parsing
+ the NDJSON files yourself.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ cursor: next_cursor from the previous page.
+
+ limit: Records per page. Defaults to 25. A page can close early so its payload stays
+ under ~8 MB; rely on next_cursor rather than counting records.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return self._get(
+ path_template("/batch/{batch_id}/results", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ batch_get_results_params.BatchGetResultsParams,
+ ),
+ ),
+ cast_to=BatchGetResultsResponse,
+ )
+
+ def submit(
+ self,
+ *,
+ identifiers: batch_submit_params.Identifiers,
+ tags: SequenceNotStr[str] | Omit = omit,
+ timeout_ms: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchSubmitResponse:
+ """
+ Retrieve and normalize a person profile from identifiers.
+
+ Args:
+ identifiers: Known identifiers for the person. At least one identifier is required.
+
+ tags: Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.
+
+ timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer
+ than this value, it will be aborted with a 408 status code. Maximum allowed
+ value is 300000ms (5 minutes).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/people/retrieve",
+ body=maybe_transform(
+ {
+ "identifiers": identifiers,
+ "tags": tags,
+ "timeout_ms": timeout_ms,
+ },
+ batch_submit_params.BatchSubmitParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchSubmitResponse,
+ )
+
+
+class AsyncBatchResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncBatchResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/context-dot-dev/context-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBatchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBatchResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/context-dot-dev/context-python-sdk#with_streaming_response
+ """
+ return AsyncBatchResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ batch_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchRetrieveResponse:
+ """Check progress and get download links when the batch finishes.
+
+ Also returns the
+ rejected-URL list and webhook signing secret from submission, so nothing is lost
+ if the submit response was dropped.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return await self._get(
+ path_template("/batch/{batch_id}", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchRetrieveResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ q: str | Omit = omit,
+ search_type: Literal["exact", "prefix"] | Omit = omit,
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] | Omit = omit,
+ tags: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchListResponse:
+ """List your batches from newest to oldest.
+
+ Filter by status or continue with a
+ cursor.
+
+ Args:
+ cursor: Cursor from the previous page.
+
+ limit: Batches per page. Defaults to 25.
+
+ q: Free-text search term, matched against the batch id, crawl source (start URL or
+ sitemap domain), and tags.
+
+ search_type: `prefix` for as-you-type prefix matching (default), `exact` for full-token
+ matching.
+
+ status: Filter by status.
+
+ tags: Comma-separated list of tags to filter by (matches batches having any of them).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/batch/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "q": q,
+ "search_type": search_type,
+ "status": status,
+ "tags": tags,
+ },
+ batch_list_params.BatchListParams,
+ ),
+ ),
+ cast_to=BatchListResponse,
+ )
+
+ async def cancel(
+ self,
+ batch_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchCancelResponse:
+ """Stop a batch from starting new pages.
+
+ In-progress pages finish, and unused
+ credits are refunded.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return await self._post(
+ path_template("/batch/{batch_id}/cancel", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchCancelResponse,
+ )
+
+ async def get_results(
+ self,
+ batch_id: str,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchGetResultsResponse:
+ """
+ Page through the result records of a finished batch as JSON, in the same order
+ as the downloadable result files. Use this instead of downloading and parsing
+ the NDJSON files yourself.
+
+ Args:
+ batch_id: ID of the batch to retrieve or cancel.
+
+ cursor: next_cursor from the previous page.
+
+ limit: Records per page. Defaults to 25. A page can close early so its payload stays
+ under ~8 MB; rely on next_cursor rather than counting records.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not batch_id:
+ raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}")
+ return await self._get(
+ path_template("/batch/{batch_id}/results", batch_id=batch_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ batch_get_results_params.BatchGetResultsParams,
+ ),
+ ),
+ cast_to=BatchGetResultsResponse,
+ )
+
+ async def submit(
+ self,
+ *,
+ identifiers: batch_submit_params.Identifiers,
+ tags: SequenceNotStr[str] | Omit = omit,
+ timeout_ms: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BatchSubmitResponse:
+ """
+ Retrieve and normalize a person profile from identifiers.
+
+ Args:
+ identifiers: Known identifiers for the person. At least one identifier is required.
+
+ tags: Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.
+
+ timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer
+ than this value, it will be aborted with a 408 status code. Maximum allowed
+ value is 300000ms (5 minutes).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/people/retrieve",
+ body=await async_maybe_transform(
+ {
+ "identifiers": identifiers,
+ "tags": tags,
+ "timeout_ms": timeout_ms,
+ },
+ batch_submit_params.BatchSubmitParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BatchSubmitResponse,
+ )
+
+
+class BatchResourceWithRawResponse:
+ def __init__(self, batch: BatchResource) -> None:
+ self._batch = batch
+
+ self.retrieve = to_raw_response_wrapper(
+ batch.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ batch.list,
+ )
+ self.cancel = to_raw_response_wrapper(
+ batch.cancel,
+ )
+ self.get_results = to_raw_response_wrapper(
+ batch.get_results,
+ )
+ self.submit = to_raw_response_wrapper(
+ batch.submit,
+ )
+
+
+class AsyncBatchResourceWithRawResponse:
+ def __init__(self, batch: AsyncBatchResource) -> None:
+ self._batch = batch
+
+ self.retrieve = async_to_raw_response_wrapper(
+ batch.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ batch.list,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ batch.cancel,
+ )
+ self.get_results = async_to_raw_response_wrapper(
+ batch.get_results,
+ )
+ self.submit = async_to_raw_response_wrapper(
+ batch.submit,
+ )
+
+
+class BatchResourceWithStreamingResponse:
+ def __init__(self, batch: BatchResource) -> None:
+ self._batch = batch
+
+ self.retrieve = to_streamed_response_wrapper(
+ batch.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ batch.list,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ batch.cancel,
+ )
+ self.get_results = to_streamed_response_wrapper(
+ batch.get_results,
+ )
+ self.submit = to_streamed_response_wrapper(
+ batch.submit,
+ )
+
+
+class AsyncBatchResourceWithStreamingResponse:
+ def __init__(self, batch: AsyncBatchResource) -> None:
+ self._batch = batch
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ batch.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ batch.list,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ batch.cancel,
+ )
+ self.get_results = async_to_streamed_response_wrapper(
+ batch.get_results,
+ )
+ self.submit = async_to_streamed_response_wrapper(
+ batch.submit,
+ )
diff --git a/src/context/dev/resources/monitors.py b/src/context/dev/resources/monitors.py
index b0e6b7a..8285f02 100644
--- a/src/context/dev/resources/monitors.py
+++ b/src/context/dev/resources/monitors.py
@@ -73,11 +73,11 @@ def with_streaming_response(self) -> MonitorsResourceWithStreamingResponse:
def create(
self,
*,
- change_detection: monitor_create_params.ChangeDetection,
name: str,
- schedule: monitor_create_params.Schedule,
target: monitor_create_params.Target,
+ change_detection: monitor_create_params.ChangeDetection | Omit = omit,
mode: Literal["web"] | Omit = omit,
+ schedule: monitor_create_params.Schedule | Omit = omit,
tags: SequenceNotStr[str] | Omit = omit,
webhook: Optional[monitor_create_params.Webhook] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -94,17 +94,17 @@ def create(
its initial baseline.
Args:
+ target: Discriminated union describing what the monitor watches.
+
change_detection: Discriminated union describing how changes are detected.
+ mode: Top-level monitor category. Always `web` today; the concrete behavior is
+ described by `target` and `change_detection`.
+
schedule: Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
every 6 hours or every 2 days. The total interval (frequency × unit) must be
between 10 minutes and 1 year.
- target: Discriminated union describing what the monitor watches.
-
- mode: Top-level monitor category. Always `web` today; the concrete behavior is
- described by `target` and `change_detection`.
-
tags: User-defined tags for grouping and filtering monitors and their changes.
Duplicates are removed.
@@ -120,11 +120,11 @@ def create(
"/monitors",
body=maybe_transform(
{
- "change_detection": change_detection,
"name": name,
- "schedule": schedule,
"target": target,
+ "change_detection": change_detection,
"mode": mode,
+ "schedule": schedule,
"tags": tags,
"webhook": webhook,
},
@@ -749,11 +749,11 @@ def with_streaming_response(self) -> AsyncMonitorsResourceWithStreamingResponse:
async def create(
self,
*,
- change_detection: monitor_create_params.ChangeDetection,
name: str,
- schedule: monitor_create_params.Schedule,
target: monitor_create_params.Target,
+ change_detection: monitor_create_params.ChangeDetection | Omit = omit,
mode: Literal["web"] | Omit = omit,
+ schedule: monitor_create_params.Schedule | Omit = omit,
tags: SequenceNotStr[str] | Omit = omit,
webhook: Optional[monitor_create_params.Webhook] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -770,17 +770,17 @@ async def create(
its initial baseline.
Args:
+ target: Discriminated union describing what the monitor watches.
+
change_detection: Discriminated union describing how changes are detected.
+ mode: Top-level monitor category. Always `web` today; the concrete behavior is
+ described by `target` and `change_detection`.
+
schedule: Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
every 6 hours or every 2 days. The total interval (frequency × unit) must be
between 10 minutes and 1 year.
- target: Discriminated union describing what the monitor watches.
-
- mode: Top-level monitor category. Always `web` today; the concrete behavior is
- described by `target` and `change_detection`.
-
tags: User-defined tags for grouping and filtering monitors and their changes.
Duplicates are removed.
@@ -796,11 +796,11 @@ async def create(
"/monitors",
body=await async_maybe_transform(
{
- "change_detection": change_detection,
"name": name,
- "schedule": schedule,
"target": target,
+ "change_detection": change_detection,
"mode": mode,
+ "schedule": schedule,
"tags": tags,
"webhook": webhook,
},
diff --git a/src/context/dev/types/__init__.py b/src/context/dev/types/__init__.py
index e1ced36..c49ba0d 100644
--- a/src/context/dev/types/__init__.py
+++ b/src/context/dev/types/__init__.py
@@ -2,20 +2,28 @@
from __future__ import annotations
+from .error import Error as Error
+from .error_count import ErrorCount as ErrorCount
from .webhook_delivery import WebhookDelivery as WebhookDelivery
+from .batch_list_params import BatchListParams as BatchListParams
from .web_search_params import WebSearchParams as WebSearchParams
from .web_extract_params import WebExtractParams as WebExtractParams
+from .batch_list_response import BatchListResponse as BatchListResponse
+from .batch_submit_params import BatchSubmitParams as BatchSubmitParams
from .monitor_list_params import MonitorListParams as MonitorListParams
from .parse_handle_params import ParseHandleParams as ParseHandleParams
from .web_search_response import WebSearchResponse as WebSearchResponse
from .monitor_run_response import MonitorRunResponse as MonitorRunResponse
from .web_extract_response import WebExtractResponse as WebExtractResponse
+from .batch_cancel_response import BatchCancelResponse as BatchCancelResponse
+from .batch_submit_response import BatchSubmitResponse as BatchSubmitResponse
from .brand_retrieve_params import BrandRetrieveParams as BrandRetrieveParams
from .monitor_create_params import MonitorCreateParams as MonitorCreateParams
from .monitor_list_response import MonitorListResponse as MonitorListResponse
from .monitor_update_params import MonitorUpdateParams as MonitorUpdateParams
from .parse_handle_response import ParseHandleResponse as ParseHandleResponse
from .web_screenshot_params import WebScreenshotParams as WebScreenshotParams
+from .batch_retrieve_response import BatchRetrieveResponse as BatchRetrieveResponse
from .brand_retrieve_response import BrandRetrieveResponse as BrandRetrieveResponse
from .monitor_create_response import MonitorCreateResponse as MonitorCreateResponse
from .monitor_delete_response import MonitorDeleteResponse as MonitorDeleteResponse
@@ -23,6 +31,7 @@
from .utility_prefetch_params import UtilityPrefetchParams as UtilityPrefetchParams
from .web_screenshot_response import WebScreenshotResponse as WebScreenshotResponse
from .web_web_crawl_md_params import WebWebCrawlMdParams as WebWebCrawlMdParams
+from .batch_get_results_params import BatchGetResultsParams as BatchGetResultsParams
from .monitor_list_runs_params import MonitorListRunsParams as MonitorListRunsParams
from .web_extract_fonts_params import WebExtractFontsParams as WebExtractFontsParams
from .web_web_scrape_md_params import WebWebScrapeMdParams as WebWebScrapeMdParams
@@ -31,6 +40,7 @@
from .utility_prefetch_response import UtilityPrefetchResponse as UtilityPrefetchResponse
from .web_web_crawl_md_response import WebWebCrawlMdResponse as WebWebCrawlMdResponse
from .ai_extract_products_params import AIExtractProductsParams as AIExtractProductsParams
+from .batch_get_results_response import BatchGetResultsResponse as BatchGetResultsResponse
from .monitor_list_runs_response import MonitorListRunsResponse as MonitorListRunsResponse
from .web_extract_fonts_response import WebExtractFontsResponse as WebExtractFontsResponse
from .web_web_scrape_html_params import WebWebScrapeHTMLParams as WebWebScrapeHTMLParams
diff --git a/src/context/dev/types/batch_cancel_response.py b/src/context/dev/types/batch_cancel_response.py
new file mode 100644
index 0000000..9ac6ff5
--- /dev/null
+++ b/src/context/dev/types/batch_cancel_response.py
@@ -0,0 +1,141 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .error import Error
+from .._models import BaseModel
+from .error_count import ErrorCount
+
+__all__ = ["BatchCancelResponse", "Credits", "Input", "Progress", "Results", "ResultsFile", "Timing", "KeyMetadata"]
+
+
+class Credits(BaseModel):
+ """Reserved and used credits."""
+
+ charged: int
+ """Credits used by successful pages."""
+
+ estimated: int
+ """Credits reserved when the batch was accepted."""
+
+
+class Input(BaseModel):
+ """Submission counts."""
+
+ accepted: int
+ """Pages accepted, or the crawl page limit. Credits are reserved for this count."""
+
+ duplicates: int
+ """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls."""
+
+ invalid: int
+ """Pages rejected during validation."""
+
+ submitted: int
+ """Pages submitted before validation. For a crawl, the page limit."""
+
+
+class Progress(BaseModel):
+ """Current processing counts. Use `status` to check completion."""
+
+ failed: int
+ """Pages that could not be scraped."""
+
+ pending: int
+ """Accepted pages not yet attempted.
+
+ Always 0 once the batch completes; a crawl can finish under its page limit when
+ the site has no more reachable pages.
+ """
+
+ succeeded: int
+ """Pages scraped successfully."""
+
+
+class ResultsFile(BaseModel):
+ bytes: int
+ """Compressed file size in bytes."""
+
+ items: int
+ """Results in this file."""
+
+ url: str
+ """Temporary URL for a gzipped NDJSON file."""
+
+
+class Results(BaseModel):
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ expires_at: str
+ """When the download URLs expire."""
+
+ files: List[ResultsFile]
+ """Result files. Order is not guaranteed."""
+
+
+class Timing(BaseModel):
+ completed_at: Optional[str] = None
+ """When processing finished. Null while active."""
+
+ created_at: str
+ """When the batch was created."""
+
+ started_at: Optional[str] = None
+ """When processing started. Null while queued."""
+
+
+class KeyMetadata(BaseModel):
+ """API key usage for this request."""
+
+ credits_consumed: int
+ """The number of credits consumed by this request."""
+
+ credits_remaining: int
+ """The number of credits remaining for your organization after this request."""
+
+
+class BatchCancelResponse(BaseModel):
+ id: str
+ """Batch ID used to retrieve or cancel the job."""
+
+ credits: Credits
+ """Reserved and used credits."""
+
+ error: Optional[Error] = None
+ """Why the batch failed."""
+
+ errors: List[ErrorCount]
+ """Page failures grouped by error code."""
+
+ input: Input
+ """Submission counts."""
+
+ mode: Literal["scrape", "crawl"]
+ """How pages are selected."""
+
+ progress: Progress
+ """Current processing counts. Use `status` to check completion."""
+
+ results: Optional[Results] = None
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"]
+ """Current state. `completed`, `cancelled`, and `failed` are final."""
+
+ tags: List[str]
+ """Tags stored on the batch at submission."""
+
+ timing: Timing
+
+ type: Literal["markdown", "html"]
+ """Output format."""
+
+ key_metadata: Optional[KeyMetadata] = None
+ """API key usage for this request."""
diff --git a/src/context/dev/types/batch_get_results_params.py b/src/context/dev/types/batch_get_results_params.py
new file mode 100644
index 0000000..cd0bb38
--- /dev/null
+++ b/src/context/dev/types/batch_get_results_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["BatchGetResultsParams"]
+
+
+class BatchGetResultsParams(TypedDict, total=False):
+ cursor: str
+ """next_cursor from the previous page."""
+
+ limit: int
+ """Records per page.
+
+ Defaults to 25. A page can close early so its payload stays under ~8 MB; rely on
+ next_cursor rather than counting records.
+ """
diff --git a/src/context/dev/types/batch_get_results_response.py b/src/context/dev/types/batch_get_results_response.py
new file mode 100644
index 0000000..13b3ee0
--- /dev/null
+++ b/src/context/dev/types/batch_get_results_response.py
@@ -0,0 +1,184 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from .._utils import PropertyInfo
+from .._models import BaseModel
+
+__all__ = [
+ "BatchGetResultsResponse",
+ "Data",
+ "DataOk",
+ "DataOkMetadata",
+ "DataOkMetadataAlternate",
+ "DataError",
+ "KeyMetadata",
+]
+
+
+class DataOkMetadataAlternate(BaseModel):
+ href: str
+ """Resolved alternate URL."""
+
+ hreflang: Optional[str] = None
+ """Language or locale for the alternate URL, when present."""
+
+ title: Optional[str] = None
+ """Alternate resource title, when present."""
+
+ type: Optional[str] = None
+ """Alternate resource MIME type, when present."""
+
+
+class DataOkMetadata(BaseModel):
+ """Metadata extracted from the scraped page HTML."""
+
+ final_url: str = FieldInfo(alias="finalUrl")
+ """Final URL scraped after redirects or scraper fallback, when known.
+
+ Falls back to sourceUrl when unavailable.
+ """
+
+ source_url: str = FieldInfo(alias="sourceUrl")
+ """Original URL requested by the caller."""
+
+ additional_meta: Optional[Dict[str, Union[str, List[str]]]] = FieldInfo(alias="additionalMeta", default=None)
+ """Additional non-social meta tags not promoted to top-level metadata fields."""
+
+ alternates: Optional[List[DataOkMetadataAlternate]] = None
+ """Resolved alternate links from link rel=alternate tags."""
+
+ author: Optional[str] = None
+ """Author metadata, when present."""
+
+ canonical_url: Optional[str] = FieldInfo(alias="canonicalUrl", default=None)
+ """Resolved canonical URL, when present."""
+
+ description: Optional[str] = None
+ """Best description extracted from standard, Open Graph, or Twitter metadata."""
+
+ favicon: Optional[str] = None
+ """Resolved favicon URL, when present."""
+
+ image: Optional[str] = None
+ """Primary resolved preview image from Open Graph, Twitter, or image metadata."""
+
+ json_ld: Optional[List[Dict[str, object]]] = FieldInfo(alias="jsonLd", default=None)
+ """JSON-LD structured data blocks parsed from the page."""
+
+ keywords: Optional[List[str]] = None
+ """Keywords extracted from the page's keywords meta tag."""
+
+ language: Optional[str] = None
+ """Language extracted from html lang or language meta tags."""
+
+ modified_time: Optional[str] = FieldInfo(alias="modifiedTime", default=None)
+ """Modified timestamp/date from page metadata, when present."""
+
+ open_graph: Optional[Dict[str, Union[str, List[str]]]] = FieldInfo(alias="openGraph", default=None)
+ """Open Graph metadata with the og: prefix removed and keys camel-cased."""
+
+ published_time: Optional[str] = FieldInfo(alias="publishedTime", default=None)
+ """Published timestamp/date from page metadata, when present."""
+
+ robots: Optional[str] = None
+ """Robots meta directive, when present."""
+
+ site_name: Optional[str] = FieldInfo(alias="siteName", default=None)
+ """Site or application name from page metadata."""
+
+ title: Optional[str] = None
+ """Best title extracted from the page."""
+
+ twitter: Optional[Dict[str, Union[str, List[str]]]] = None
+ """Twitter card metadata with the twitter: prefix removed and keys camel-cased."""
+
+
+class DataOk(BaseModel):
+ """A page the batch fetched successfully."""
+
+ final_url: str
+ """URL the content was read from, after redirects."""
+
+ http_status: Optional[int] = None
+ """HTTP status of the final response, when known."""
+
+ metadata: DataOkMetadata
+ """Metadata extracted from the scraped page HTML."""
+
+ status: Literal["ok"]
+ """The page was scraped."""
+
+ url: str
+ """URL as submitted, or as discovered by the crawl."""
+
+ html: Optional[str] = None
+ """Raw page HTML. Present on html batches."""
+
+ item_id: Optional[str] = FieldInfo(alias="itemId", default=None)
+ """Caller-supplied identifier echoed from submission."""
+
+ markdown: Optional[str] = None
+ """Page content as Markdown. Present on markdown batches."""
+
+ meta: Optional[Dict[str, object]] = None
+ """Caller-supplied metadata echoed from submission."""
+
+
+class DataError(BaseModel):
+ """A page the batch could not fetch."""
+
+ error_code: str
+ """Why the page failed."""
+
+ message: str
+ """Human-readable failure detail."""
+
+ status: Literal["error"]
+ """The page could not be scraped."""
+
+ url: str
+ """URL as submitted, or as discovered by the crawl."""
+
+ item_id: Optional[str] = FieldInfo(alias="itemId", default=None)
+ """Caller-supplied identifier echoed from submission."""
+
+ meta: Optional[Dict[str, object]] = None
+ """Caller-supplied metadata echoed from submission."""
+
+
+Data: TypeAlias = Annotated[Union[DataOk, DataError], PropertyInfo(discriminator="status")]
+
+
+class KeyMetadata(BaseModel):
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the response status is not 200.
+ """
+
+ credits_consumed: int
+ """The number of credits consumed by this request."""
+
+ credits_remaining: int
+ """The number of credits remaining for your organization after this request."""
+
+
+class BatchGetResultsResponse(BaseModel):
+ data: Optional[List[Data]] = None
+ """Result records on this page."""
+
+ has_more: Optional[bool] = None
+ """Whether another page is available."""
+
+ key_metadata: Optional[KeyMetadata] = None
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the
+ response status is not 200.
+ """
+
+ next_cursor: Optional[str] = None
+ """Cursor for the next page."""
diff --git a/src/context/dev/types/batch_list_params.py b/src/context/dev/types/batch_list_params.py
new file mode 100644
index 0000000..276350b
--- /dev/null
+++ b/src/context/dev/types/batch_list_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["BatchListParams"]
+
+
+class BatchListParams(TypedDict, total=False):
+ cursor: str
+ """Cursor from the previous page."""
+
+ limit: int
+ """Batches per page. Defaults to 25."""
+
+ q: str
+ """
+ Free-text search term, matched against the batch id, crawl source (start URL or
+ sitemap domain), and tags.
+ """
+
+ search_type: Literal["exact", "prefix"]
+ """
+ `prefix` for as-you-type prefix matching (default), `exact` for full-token
+ matching.
+ """
+
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"]
+ """Filter by status."""
+
+ tags: str
+ """Comma-separated list of tags to filter by (matches batches having any of them)."""
diff --git a/src/context/dev/types/batch_list_response.py b/src/context/dev/types/batch_list_response.py
new file mode 100644
index 0000000..c91eb07
--- /dev/null
+++ b/src/context/dev/types/batch_list_response.py
@@ -0,0 +1,171 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .error import Error
+from .._models import BaseModel
+from .error_count import ErrorCount
+
+__all__ = [
+ "BatchListResponse",
+ "Data",
+ "DataCredits",
+ "DataInput",
+ "DataProgress",
+ "DataResults",
+ "DataResultsFile",
+ "DataTiming",
+ "KeyMetadata",
+]
+
+
+class DataCredits(BaseModel):
+ """Reserved and used credits."""
+
+ charged: int
+ """Credits used by successful pages."""
+
+ estimated: int
+ """Credits reserved when the batch was accepted."""
+
+
+class DataInput(BaseModel):
+ """Submission counts."""
+
+ accepted: int
+ """Pages accepted, or the crawl page limit. Credits are reserved for this count."""
+
+ duplicates: int
+ """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls."""
+
+ invalid: int
+ """Pages rejected during validation."""
+
+ submitted: int
+ """Pages submitted before validation. For a crawl, the page limit."""
+
+
+class DataProgress(BaseModel):
+ """Current processing counts. Use `status` to check completion."""
+
+ failed: int
+ """Pages that could not be scraped."""
+
+ pending: int
+ """Accepted pages not yet attempted.
+
+ Always 0 once the batch completes; a crawl can finish under its page limit when
+ the site has no more reachable pages.
+ """
+
+ succeeded: int
+ """Pages scraped successfully."""
+
+
+class DataResultsFile(BaseModel):
+ bytes: int
+ """Compressed file size in bytes."""
+
+ items: int
+ """Results in this file."""
+
+ url: str
+ """Temporary URL for a gzipped NDJSON file."""
+
+
+class DataResults(BaseModel):
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ expires_at: str
+ """When the download URLs expire."""
+
+ files: List[DataResultsFile]
+ """Result files. Order is not guaranteed."""
+
+
+class DataTiming(BaseModel):
+ completed_at: Optional[str] = None
+ """When processing finished. Null while active."""
+
+ created_at: str
+ """When the batch was created."""
+
+ started_at: Optional[str] = None
+ """When processing started. Null while queued."""
+
+
+class Data(BaseModel):
+ """An asynchronous web scraping job."""
+
+ id: str
+ """Batch ID used to retrieve or cancel the job."""
+
+ credits: DataCredits
+ """Reserved and used credits."""
+
+ error: Optional[Error] = None
+ """Why the batch failed."""
+
+ errors: List[ErrorCount]
+ """Page failures grouped by error code."""
+
+ input: DataInput
+ """Submission counts."""
+
+ mode: Literal["scrape", "crawl"]
+ """How pages are selected."""
+
+ progress: DataProgress
+ """Current processing counts. Use `status` to check completion."""
+
+ results: Optional[DataResults] = None
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"]
+ """Current state. `completed`, `cancelled`, and `failed` are final."""
+
+ tags: List[str]
+ """Tags stored on the batch at submission."""
+
+ timing: DataTiming
+
+ type: Literal["markdown", "html"]
+ """Output format."""
+
+
+class KeyMetadata(BaseModel):
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the response status is not 200.
+ """
+
+ credits_consumed: int
+ """The number of credits consumed by this request."""
+
+ credits_remaining: int
+ """The number of credits remaining for your organization after this request."""
+
+
+class BatchListResponse(BaseModel):
+ data: Optional[List[Data]] = None
+ """Batches on this page."""
+
+ has_more: Optional[bool] = None
+ """Whether another page is available."""
+
+ key_metadata: Optional[KeyMetadata] = None
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the
+ response status is not 200.
+ """
+
+ next_cursor: Optional[str] = None
+ """Cursor for the next page."""
diff --git a/src/context/dev/types/batch_retrieve_response.py b/src/context/dev/types/batch_retrieve_response.py
new file mode 100644
index 0000000..d2ef3e4
--- /dev/null
+++ b/src/context/dev/types/batch_retrieve_response.py
@@ -0,0 +1,165 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .error import Error
+from .._models import BaseModel
+from .error_count import ErrorCount
+
+__all__ = [
+ "BatchRetrieveResponse",
+ "Credits",
+ "Input",
+ "InvalidURL",
+ "Progress",
+ "Results",
+ "ResultsFile",
+ "Timing",
+ "KeyMetadata",
+]
+
+
+class Credits(BaseModel):
+ """Reserved and used credits."""
+
+ charged: int
+ """Credits used by successful pages."""
+
+ estimated: int
+ """Credits reserved when the batch was accepted."""
+
+
+class Input(BaseModel):
+ """Submission counts."""
+
+ accepted: int
+ """Pages accepted, or the crawl page limit. Credits are reserved for this count."""
+
+ duplicates: int
+ """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls."""
+
+ invalid: int
+ """Pages rejected during validation."""
+
+ submitted: int
+ """Pages submitted before validation. For a crawl, the page limit."""
+
+
+class InvalidURL(BaseModel):
+ reason: str
+ """Why it was rejected."""
+
+ url: str
+ """Rejected URL."""
+
+
+class Progress(BaseModel):
+ """Current processing counts. Use `status` to check completion."""
+
+ failed: int
+ """Pages that could not be scraped."""
+
+ pending: int
+ """Accepted pages not yet attempted.
+
+ Always 0 once the batch completes; a crawl can finish under its page limit when
+ the site has no more reachable pages.
+ """
+
+ succeeded: int
+ """Pages scraped successfully."""
+
+
+class ResultsFile(BaseModel):
+ bytes: int
+ """Compressed file size in bytes."""
+
+ items: int
+ """Results in this file."""
+
+ url: str
+ """Temporary URL for a gzipped NDJSON file."""
+
+
+class Results(BaseModel):
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ expires_at: str
+ """When the download URLs expire."""
+
+ files: List[ResultsFile]
+ """Result files. Order is not guaranteed."""
+
+
+class Timing(BaseModel):
+ completed_at: Optional[str] = None
+ """When processing finished. Null while active."""
+
+ created_at: str
+ """When the batch was created."""
+
+ started_at: Optional[str] = None
+ """When processing started. Null while queued."""
+
+
+class KeyMetadata(BaseModel):
+ """API key usage for this request."""
+
+ credits_consumed: int
+ """The number of credits consumed by this request."""
+
+ credits_remaining: int
+ """The number of credits remaining for your organization after this request."""
+
+
+class BatchRetrieveResponse(BaseModel):
+ id: str
+ """Batch ID used to retrieve or cancel the job."""
+
+ credits: Credits
+ """Reserved and used credits."""
+
+ error: Optional[Error] = None
+ """Why the batch failed."""
+
+ errors: List[ErrorCount]
+ """Page failures grouped by error code."""
+
+ input: Input
+ """Submission counts."""
+
+ invalid_urls: List[InvalidURL]
+ """Rejected URLs, up to 100. These are not charged."""
+
+ mode: Literal["scrape", "crawl"]
+ """How pages are selected."""
+
+ progress: Progress
+ """Current processing counts. Use `status` to check completion."""
+
+ results: Optional[Results] = None
+ """Download links available when the batch finishes.
+
+ GET /batch/{batch_id}/results serves the same records as paginated JSON.
+ """
+
+ status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"]
+ """Current state. `completed`, `cancelled`, and `failed` are final."""
+
+ tags: List[str]
+ """Tags stored on the batch at submission."""
+
+ timing: Timing
+
+ type: Literal["markdown", "html"]
+ """Output format."""
+
+ key_metadata: Optional[KeyMetadata] = None
+ """API key usage for this request."""
+
+ webhook_secret: Optional[str] = None
+ """Webhook signing secret. Also returned by GET /batch/{batch_id}."""
diff --git a/src/context/dev/types/batch_submit_params.py b/src/context/dev/types/batch_submit_params.py
new file mode 100644
index 0000000..3ae0986
--- /dev/null
+++ b/src/context/dev/types/batch_submit_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = ["BatchSubmitParams", "Identifiers"]
+
+
+class BatchSubmitParams(TypedDict, total=False):
+ identifiers: Required[Identifiers]
+ """Known identifiers for the person. At least one identifier is required."""
+
+ tags: SequenceNotStr[str]
+ """Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters."""
+
+ timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")]
+ """Optional timeout in milliseconds for the request.
+
+ If the request takes longer than this value, it will be aborted with a 408
+ status code. Maximum allowed value is 300000ms (5 minutes).
+ """
+
+
+class Identifiers(TypedDict, total=False):
+ """Known identifiers for the person. At least one identifier is required."""
+
+ linkedin_url: Annotated[str, PropertyInfo(alias="linkedinUrl")]
+ """LinkedIn profile URL, e.g. https://www.linkedin.com/in/yahia-bakour/."""
diff --git a/src/context/dev/types/batch_submit_response.py b/src/context/dev/types/batch_submit_response.py
new file mode 100644
index 0000000..6fd2690
--- /dev/null
+++ b/src/context/dev/types/batch_submit_response.py
@@ -0,0 +1,267 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = [
+ "BatchSubmitResponse",
+ "Metadata",
+ "MetadataIdentifiers",
+ "Person",
+ "PersonEducation",
+ "PersonEducationInstitution",
+ "PersonEducationDates",
+ "PersonEducationDatesEndDate",
+ "PersonEducationDatesStartDate",
+ "PersonExperience",
+ "PersonExperienceCompany",
+ "PersonExperienceDates",
+ "PersonExperienceDatesEndDate",
+ "PersonExperienceDatesStartDate",
+ "PersonProfile",
+ "PersonSkill",
+ "KeyMetadata",
+]
+
+
+class MetadataIdentifiers(BaseModel):
+ """Identifiers returned for the person."""
+
+ linkedin_url: Optional[str] = FieldInfo(alias="linkedinUrl", default=None)
+ """LinkedIn profile URL."""
+
+
+class Metadata(BaseModel):
+ """Additional response details."""
+
+ identifiers: MetadataIdentifiers
+ """Identifiers returned for the person."""
+
+ sources_attempted: List[Literal["linkedin", "cv", "manual", "github", "other"]] = FieldInfo(
+ alias="sourcesAttempted"
+ )
+ """Source categories checked."""
+
+ sources_succeeded: List[Literal["linkedin", "cv", "manual", "github", "other"]] = FieldInfo(
+ alias="sourcesSucceeded"
+ )
+ """Source categories with data."""
+
+ urls_analyzed: List[str] = FieldInfo(alias="urlsAnalyzed")
+ """URLs reviewed for this profile."""
+
+ personal_website_url: Optional[str] = FieldInfo(alias="personalWebsiteUrl", default=None)
+ """Personal website URL, when found."""
+
+
+class PersonEducationInstitution(BaseModel):
+ """School or institution name."""
+
+ display: str
+ """Display name."""
+
+ normalized: Optional[str] = None
+ """Standardized name, when available."""
+
+
+class PersonEducationDatesEndDate(BaseModel):
+ """End date, when known."""
+
+ year: int
+ """Year value."""
+
+ day: Optional[int] = None
+ """Day value, when known."""
+
+ month: Optional[int] = None
+ """Month value, when known."""
+
+
+class PersonEducationDatesStartDate(BaseModel):
+ """Start date, when known."""
+
+ year: int
+ """Year value."""
+
+ day: Optional[int] = None
+ """Day value, when known."""
+
+ month: Optional[int] = None
+ """Month value, when known."""
+
+
+class PersonEducationDates(BaseModel):
+ """Education dates."""
+
+ end_date: Optional[PersonEducationDatesEndDate] = FieldInfo(alias="endDate", default=None)
+ """End date, when known."""
+
+ is_current: Optional[bool] = FieldInfo(alias="isCurrent", default=None)
+ """Whether the entry is current."""
+
+ start_date: Optional[PersonEducationDatesStartDate] = FieldInfo(alias="startDate", default=None)
+ """Start date, when known."""
+
+
+class PersonEducation(BaseModel):
+ institution: PersonEducationInstitution
+ """School or institution name."""
+
+ dates: Optional[PersonEducationDates] = None
+ """Education dates."""
+
+ description: Optional[str] = None
+ """Additional education details."""
+
+ field_of_study: Optional[str] = FieldInfo(alias="fieldOfStudy", default=None)
+ """Area of study."""
+
+ qualification: Optional[str] = None
+ """Degree, certificate, or credential."""
+
+
+class PersonExperienceCompany(BaseModel):
+ """Company or organization name."""
+
+ display: str
+ """Display name."""
+
+ normalized: Optional[str] = None
+ """Standardized name, when available."""
+
+
+class PersonExperienceDatesEndDate(BaseModel):
+ """End date, when known."""
+
+ year: int
+ """Year value."""
+
+ day: Optional[int] = None
+ """Day value, when known."""
+
+ month: Optional[int] = None
+ """Month value, when known."""
+
+
+class PersonExperienceDatesStartDate(BaseModel):
+ """Start date, when known."""
+
+ year: int
+ """Year value."""
+
+ day: Optional[int] = None
+ """Day value, when known."""
+
+ month: Optional[int] = None
+ """Month value, when known."""
+
+
+class PersonExperienceDates(BaseModel):
+ """Role dates."""
+
+ end_date: Optional[PersonExperienceDatesEndDate] = FieldInfo(alias="endDate", default=None)
+ """End date, when known."""
+
+ is_current: Optional[bool] = FieldInfo(alias="isCurrent", default=None)
+ """Whether the entry is current."""
+
+ start_date: Optional[PersonExperienceDatesStartDate] = FieldInfo(alias="startDate", default=None)
+ """Start date, when known."""
+
+
+class PersonExperience(BaseModel):
+ company: PersonExperienceCompany
+ """Company or organization name."""
+
+ title: str
+ """Role or job title."""
+
+ dates: Optional[PersonExperienceDates] = None
+ """Role dates."""
+
+ description: Optional[str] = None
+ """Role description."""
+
+
+class PersonProfile(BaseModel):
+ """Core profile details."""
+
+ full_name: Optional[str] = FieldInfo(alias="fullName", default=None)
+ """Person's full name."""
+
+ headline: Optional[str] = None
+ """Short professional headline."""
+
+ location: Optional[str] = None
+ """Person's listed location."""
+
+ profile_picture_url: Optional[str] = FieldInfo(alias="profilePictureUrl", default=None)
+ """Profile image URL."""
+
+ summary: Optional[str] = None
+ """Brief profile summary."""
+
+
+class PersonSkill(BaseModel):
+ name: str
+ """Skill name."""
+
+ normalized: Optional[str] = None
+ """Standardized skill name, when available."""
+
+ proficiency: Optional[str] = None
+ """Skill proficiency, when available."""
+
+
+class Person(BaseModel):
+ """Retrieved person profile."""
+
+ education: List[PersonEducation]
+ """Education history."""
+
+ experience: List[PersonExperience]
+ """Work history."""
+
+ profile: PersonProfile
+ """Core profile details."""
+
+ skills: List[PersonSkill]
+ """Listed skills."""
+
+
+class KeyMetadata(BaseModel):
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the response status is not 200.
+ """
+
+ credits_consumed: int
+ """The number of credits consumed by this request."""
+
+ credits_remaining: int
+ """The number of credits remaining for your organization after this request."""
+
+
+class BatchSubmitResponse(BaseModel):
+ code: Literal[200]
+ """HTTP status code."""
+
+ metadata: Metadata
+ """Additional response details."""
+
+ person: Person
+ """Retrieved person profile."""
+
+ status: Literal["ok"]
+ """Response status."""
+
+ key_metadata: Optional[KeyMetadata] = None
+ """Metadata about the API key used for the request.
+
+ Included in every response whenever a valid API key is provided, even when the
+ response status is not 200.
+ """
diff --git a/src/context/dev/types/error.py b/src/context/dev/types/error.py
new file mode 100644
index 0000000..c564450
--- /dev/null
+++ b/src/context/dev/types/error.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["Error"]
+
+
+class Error(BaseModel):
+ """Why the batch failed."""
+
+ code: str
+ """Batch error code."""
+
+ message: str
+ """Batch error message."""
diff --git a/src/context/dev/types/error_count.py b/src/context/dev/types/error_count.py
new file mode 100644
index 0000000..783906d
--- /dev/null
+++ b/src/context/dev/types/error_count.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["ErrorCount"]
+
+
+class ErrorCount(BaseModel):
+ """Page failures sharing one error code."""
+
+ code: str
+ """Error code for these failures."""
+
+ count: int
+ """Pages that failed with this code."""
diff --git a/src/context/dev/types/monitor_create_params.py b/src/context/dev/types/monitor_create_params.py
index bdf55ea..d621a74 100644
--- a/src/context/dev/types/monitor_create_params.py
+++ b/src/context/dev/types/monitor_create_params.py
@@ -9,34 +9,27 @@
__all__ = [
"MonitorCreateParams",
- "ChangeDetection",
- "ChangeDetectionMonitorsExactChangeDetection",
- "ChangeDetectionMonitorsSemanticChangeDetection",
- "Schedule",
"Target",
"TargetMonitorsPageTarget",
"TargetMonitorsSitemapTarget",
"TargetMonitorsExtractTarget",
+ "ChangeDetection",
+ "ChangeDetectionMonitorsExactChangeDetection",
+ "ChangeDetectionMonitorsSemanticChangeDetection",
+ "Schedule",
"Webhook",
]
class MonitorCreateParams(TypedDict, total=False):
- change_detection: Required[ChangeDetection]
- """Discriminated union describing how changes are detected."""
-
name: Required[str]
- schedule: Required[Schedule]
- """Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
-
- every 6 hours or every 2 days. The total interval (frequency × unit) must be
- between 10 minutes and 1 year.
- """
-
target: Required[Target]
"""Discriminated union describing what the monitor watches."""
+ change_detection: ChangeDetection
+ """Discriminated union describing how changes are detected."""
+
mode: Literal["web"]
"""Top-level monitor category.
@@ -44,6 +37,13 @@ class MonitorCreateParams(TypedDict, total=False):
`change_detection`.
"""
+ schedule: Schedule
+ """Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
+
+ every 6 hours or every 2 days. The total interval (frequency × unit) must be
+ between 10 minutes and 1 year.
+ """
+
tags: SequenceNotStr[str]
"""User-defined tags for grouping and filtering monitors and their changes.
@@ -53,56 +53,22 @@ class MonitorCreateParams(TypedDict, total=False):
webhook: Optional[Webhook]
-class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False):
- """Detect exact changes.
-
- For page targets, this means visible text diffs. For sitemap targets, this means URL additions and removals.
- """
-
- type: Required[Literal["exact"]]
-
+class TargetMonitorsPageTarget(TypedDict, total=False):
+ """Watch a single web page.
-class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False):
- """
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
"""
- type: Required[Literal["semantic"]]
-
- confidence_threshold: float
-
-
-ChangeDetection: TypeAlias = Union[
- ChangeDetectionMonitorsExactChangeDetection, ChangeDetectionMonitorsSemanticChangeDetection
-]
+ type: Required[Literal["page"]]
+ url: Required[str]
-class Schedule(TypedDict, total=False):
- """Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
+ instructions: str
+ """Plain-language goal describing which page changes matter.
- every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year.
+ When provided without change_detection, semantic detection is inferred.
"""
- frequency: Required[int]
- """Number of units between runs.
-
- The resulting interval (frequency × unit) must be at least 10 minutes and at
- most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is
- days).
- """
-
- type: Required[Literal["interval"]]
-
- unit: Required[Literal["minutes", "hours", "days"]]
-
-
-class TargetMonitorsPageTarget(TypedDict, total=False):
- """Watch a single web page."""
-
- type: Required[Literal["page"]]
-
- url: Required[str]
-
normalize_whitespace: bool
"""Normalize whitespace before comparing or analyzing text."""
@@ -169,6 +135,49 @@ class TargetMonitorsExtractTarget(TypedDict, total=False):
Target: TypeAlias = Union[TargetMonitorsPageTarget, TargetMonitorsSitemapTarget, TargetMonitorsExtractTarget]
+class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False):
+ """Detect exact changes.
+
+ For page targets, this means visible text diffs. For sitemap targets, this means URL additions and removals.
+ """
+
+ type: Required[Literal["exact"]]
+
+
+class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False):
+ """
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
+ """
+
+ type: Required[Literal["semantic"]]
+
+ confidence_threshold: float
+
+
+ChangeDetection: TypeAlias = Union[
+ ChangeDetectionMonitorsExactChangeDetection, ChangeDetectionMonitorsSemanticChangeDetection
+]
+
+
+class Schedule(TypedDict, total=False):
+ """Run the monitor on a fixed interval defined by a frequency and a unit, e.g.
+
+ every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year.
+ """
+
+ frequency: Required[int]
+ """Number of units between runs.
+
+ The resulting interval (frequency × unit) must be at least 10 minutes and at
+ most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is
+ days).
+ """
+
+ type: Required[Literal["interval"]]
+
+ unit: Required[Literal["minutes", "hours", "days"]]
+
+
class Webhook(TypedDict, total=False):
url: Required[str]
"""Webhook URL events are delivered to."""
diff --git a/src/context/dev/types/monitor_create_response.py b/src/context/dev/types/monitor_create_response.py
index 0a23ad1..cb47069 100644
--- a/src/context/dev/types/monitor_create_response.py
+++ b/src/context/dev/types/monitor_create_response.py
@@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel):
class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel):
"""
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
"""
type: Literal["semantic"]
@@ -74,12 +74,21 @@ class Schedule(BaseModel):
class TargetMonitorsPageTarget(BaseModel):
- """Watch a single web page."""
+ """Watch a single web page.
+
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
+ """
type: Literal["page"]
url: str
+ instructions: Optional[str] = None
+ """Plain-language goal describing which page changes matter.
+
+ When provided without change_detection, semantic detection is inferred.
+ """
+
normalize_whitespace: Optional[bool] = None
"""Normalize whitespace before comparing or analyzing text."""
@@ -251,9 +260,8 @@ class WebhookFailure(BaseModel):
class MonitorCreateResponse(BaseModel):
- """A web monitor.
-
- `mode` is the constant `web`; behavior is described by `target` (page/sitemap/extract) and `change_detection` (exact/semantic).
+ """
+ A newly created monitor plus `initial_run_id`, the id of the baseline run queued at creation.
"""
id: str
@@ -263,6 +271,13 @@ class MonitorCreateResponse(BaseModel):
created_at: datetime
+ initial_run_id: Optional[str] = None
+ """
+ The baseline run queued by this create call, or null if it could not be queued
+ immediately (in which case the baseline runs on the next scheduled tick). Poll
+ GET /monitors/{monitor_id}/runs/{run_id}.
+ """
+
mode: Literal["web"]
"""Top-level monitor category.
diff --git a/src/context/dev/types/monitor_list_response.py b/src/context/dev/types/monitor_list_response.py
index 475b767..81d9c81 100644
--- a/src/context/dev/types/monitor_list_response.py
+++ b/src/context/dev/types/monitor_list_response.py
@@ -41,7 +41,7 @@ class DataChangeDetectionMonitorsExactChangeDetection(BaseModel):
class DataChangeDetectionMonitorsSemanticChangeDetection(BaseModel):
"""
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
"""
type: Literal["semantic"]
@@ -75,12 +75,21 @@ class DataSchedule(BaseModel):
class DataTargetMonitorsPageTarget(BaseModel):
- """Watch a single web page."""
+ """Watch a single web page.
+
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
+ """
type: Literal["page"]
url: str
+ instructions: Optional[str] = None
+ """Plain-language goal describing which page changes matter.
+
+ When provided without change_detection, semantic detection is inferred.
+ """
+
normalize_whitespace: Optional[bool] = None
"""Normalize whitespace before comparing or analyzing text."""
diff --git a/src/context/dev/types/monitor_retrieve_response.py b/src/context/dev/types/monitor_retrieve_response.py
index e47be52..d96cd96 100644
--- a/src/context/dev/types/monitor_retrieve_response.py
+++ b/src/context/dev/types/monitor_retrieve_response.py
@@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel):
class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel):
"""
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
"""
type: Literal["semantic"]
@@ -74,12 +74,21 @@ class Schedule(BaseModel):
class TargetMonitorsPageTarget(BaseModel):
- """Watch a single web page."""
+ """Watch a single web page.
+
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
+ """
type: Literal["page"]
url: str
+ instructions: Optional[str] = None
+ """Plain-language goal describing which page changes matter.
+
+ When provided without change_detection, semantic detection is inferred.
+ """
+
normalize_whitespace: Optional[bool] = None
"""Normalize whitespace before comparing or analyzing text."""
diff --git a/src/context/dev/types/monitor_update_params.py b/src/context/dev/types/monitor_update_params.py
index da61ef0..ad9e94b 100644
--- a/src/context/dev/types/monitor_update_params.py
+++ b/src/context/dev/types/monitor_update_params.py
@@ -60,7 +60,7 @@ class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False):
class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False):
"""
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
"""
type: Required[Literal["semantic"]]
@@ -93,12 +93,21 @@ class Schedule(TypedDict, total=False):
class TargetMonitorsPageTarget(TypedDict, total=False):
- """Watch a single web page."""
+ """Watch a single web page.
+
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
+ """
type: Required[Literal["page"]]
url: Required[str]
+ instructions: str
+ """Plain-language goal describing which page changes matter.
+
+ When provided without change_detection, semantic detection is inferred.
+ """
+
normalize_whitespace: bool
"""Normalize whitespace before comparing or analyzing text."""
diff --git a/src/context/dev/types/monitor_update_response.py b/src/context/dev/types/monitor_update_response.py
index 61ee9ef..0d70014 100644
--- a/src/context/dev/types/monitor_update_response.py
+++ b/src/context/dev/types/monitor_update_response.py
@@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel):
class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel):
"""
- Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided).
+ Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided).
"""
type: Literal["semantic"]
@@ -74,12 +74,21 @@ class Schedule(BaseModel):
class TargetMonitorsPageTarget(BaseModel):
- """Watch a single web page."""
+ """Watch a single web page.
+
+ Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`.
+ """
type: Literal["page"]
url: str
+ instructions: Optional[str] = None
+ """Plain-language goal describing which page changes matter.
+
+ When provided without change_detection, semantic detection is inferred.
+ """
+
normalize_whitespace: Optional[bool] = None
"""Normalize whitespace before comparing or analyzing text."""
diff --git a/src/context/dev/types/web_web_scrape_html_response.py b/src/context/dev/types/web_web_scrape_html_response.py
index a1d07e8..6ea2dd9 100644
--- a/src/context/dev/types/web_web_scrape_html_response.py
+++ b/src/context/dev/types/web_web_scrape_html_response.py
@@ -7,7 +7,7 @@
from .._models import BaseModel
-__all__ = ["WebWebScrapeHTMLResponse", "Metadata", "MetadataAlternate", "KeyMetadata"]
+__all__ = ["WebWebScrapeHTMLResponse", "Metadata", "MetadataAlternate", "ActionsApplied", "KeyMetadata"]
class MetadataAlternate(BaseModel):
@@ -88,6 +88,27 @@ class Metadata(BaseModel):
"""Twitter card metadata with the twitter: prefix removed and keys camel-cased."""
+class ActionsApplied(BaseModel):
+ instruction: str
+
+ status: Literal["applied", "failed", "skipped"]
+ """Applied means the requested page state was visibly verified.
+
+ Failed means it was not verified. Skipped means it was not attempted.
+ """
+
+ completion_evidence: Optional[str] = FieldInfo(alias="completionEvidence", default=None)
+ """Visible page evidence used to verify an applied action."""
+
+ duration_ms: Optional[float] = FieldInfo(alias="durationMs", default=None)
+
+ error: Optional[str] = None
+
+ method: Optional[str] = None
+
+ target_description: Optional[str] = FieldInfo(alias="targetDescription", default=None)
+
+
class KeyMetadata(BaseModel):
"""Metadata about the API key used for the request.
@@ -130,6 +151,15 @@ class WebWebScrapeHTMLResponse(BaseModel):
url: str
"""The URL that was scraped"""
+ actions_applied: Optional[List[ActionsApplied]] = FieldInfo(alias="actionsApplied", default=None)
+ """One verified outcome per requested browser action, in request order."""
+
+ actions_html_stale: Optional[bool] = FieldInfo(alias="actionsHtmlStale", default=None)
+ """
+ True when an action was applied but the returned content could not be refreshed
+ afterward.
+ """
+
key_metadata: Optional[KeyMetadata] = None
"""Metadata about the API key used for the request.
diff --git a/src/context/dev/types/web_web_scrape_md_response.py b/src/context/dev/types/web_web_scrape_md_response.py
index 1005d9e..e02866b 100644
--- a/src/context/dev/types/web_web_scrape_md_response.py
+++ b/src/context/dev/types/web_web_scrape_md_response.py
@@ -7,7 +7,7 @@
from .._models import BaseModel
-__all__ = ["WebWebScrapeMdResponse", "Metadata", "MetadataAlternate", "KeyMetadata"]
+__all__ = ["WebWebScrapeMdResponse", "Metadata", "MetadataAlternate", "ActionsApplied", "KeyMetadata"]
class MetadataAlternate(BaseModel):
@@ -88,6 +88,27 @@ class Metadata(BaseModel):
"""Twitter card metadata with the twitter: prefix removed and keys camel-cased."""
+class ActionsApplied(BaseModel):
+ instruction: str
+
+ status: Literal["applied", "failed", "skipped"]
+ """Applied means the requested page state was visibly verified.
+
+ Failed means it was not verified. Skipped means it was not attempted.
+ """
+
+ completion_evidence: Optional[str] = FieldInfo(alias="completionEvidence", default=None)
+ """Visible page evidence used to verify an applied action."""
+
+ duration_ms: Optional[float] = FieldInfo(alias="durationMs", default=None)
+
+ error: Optional[str] = None
+
+ method: Optional[str] = None
+
+ target_description: Optional[str] = FieldInfo(alias="targetDescription", default=None)
+
+
class KeyMetadata(BaseModel):
"""Metadata about the API key used for the request.
@@ -121,6 +142,15 @@ class WebWebScrapeMdResponse(BaseModel):
url: str
"""The URL that was scraped"""
+ actions_applied: Optional[List[ActionsApplied]] = FieldInfo(alias="actionsApplied", default=None)
+ """One verified outcome per requested browser action, in request order."""
+
+ actions_html_stale: Optional[bool] = FieldInfo(alias="actionsHtmlStale", default=None)
+ """
+ True when an action was applied but the returned content could not be refreshed
+ afterward.
+ """
+
key_metadata: Optional[KeyMetadata] = None
"""Metadata about the API key used for the request.
diff --git a/tests/api_resources/test_batch.py b/tests/api_resources/test_batch.py
new file mode 100644
index 0000000..2cce1cf
--- /dev/null
+++ b/tests/api_resources/test_batch.py
@@ -0,0 +1,472 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from context.dev import ContextDev, AsyncContextDev
+from tests.utils import assert_matches_type
+from context.dev.types import (
+ BatchListResponse,
+ BatchCancelResponse,
+ BatchSubmitResponse,
+ BatchRetrieveResponse,
+ BatchGetResultsResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBatch:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: ContextDev) -> None:
+ batch = client.batch.retrieve(
+ "batch_9f2c8a",
+ )
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: ContextDev) -> None:
+ response = client.batch.with_raw_response.retrieve(
+ "batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = response.parse()
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: ContextDev) -> None:
+ with client.batch.with_streaming_response.retrieve(
+ "batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = response.parse()
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: ContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ client.batch.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: ContextDev) -> None:
+ batch = client.batch.list()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: ContextDev) -> None:
+ batch = client.batch.list(
+ cursor="cursor",
+ limit=1,
+ q="batch_1a2b",
+ search_type="exact",
+ status="queued",
+ tags="docs,competitor",
+ )
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: ContextDev) -> None:
+ response = client.batch.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = response.parse()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: ContextDev) -> None:
+ with client.batch.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = response.parse()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: ContextDev) -> None:
+ batch = client.batch.cancel(
+ "batch_9f2c8a",
+ )
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: ContextDev) -> None:
+ response = client.batch.with_raw_response.cancel(
+ "batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = response.parse()
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: ContextDev) -> None:
+ with client.batch.with_streaming_response.cancel(
+ "batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = response.parse()
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: ContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ client.batch.with_raw_response.cancel(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_results(self, client: ContextDev) -> None:
+ batch = client.batch.get_results(
+ batch_id="batch_9f2c8a",
+ )
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_results_with_all_params(self, client: ContextDev) -> None:
+ batch = client.batch.get_results(
+ batch_id="batch_9f2c8a",
+ cursor="cursor",
+ limit=1,
+ )
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get_results(self, client: ContextDev) -> None:
+ response = client.batch.with_raw_response.get_results(
+ batch_id="batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = response.parse()
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get_results(self, client: ContextDev) -> None:
+ with client.batch.with_streaming_response.get_results(
+ batch_id="batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = response.parse()
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_get_results(self, client: ContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ client.batch.with_raw_response.get_results(
+ batch_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_submit(self, client: ContextDev) -> None:
+ batch = client.batch.submit(
+ identifiers={},
+ )
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_submit_with_all_params(self, client: ContextDev) -> None:
+ batch = client.batch.submit(
+ identifiers={"linkedin_url": "https://www.linkedin.com/in/yahia-bakour/"},
+ tags=["production", "team-alpha"],
+ timeout_ms=1000,
+ )
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_submit(self, client: ContextDev) -> None:
+ response = client.batch.with_raw_response.submit(
+ identifiers={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = response.parse()
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_submit(self, client: ContextDev) -> None:
+ with client.batch.with_streaming_response.submit(
+ identifiers={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = response.parse()
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncBatch:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.retrieve(
+ "batch_9f2c8a",
+ )
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncContextDev) -> None:
+ response = await async_client.batch.with_raw_response.retrieve(
+ "batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = await response.parse()
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncContextDev) -> None:
+ async with async_client.batch.with_streaming_response.retrieve(
+ "batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = await response.parse()
+ assert_matches_type(BatchRetrieveResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ await async_client.batch.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.list()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.list(
+ cursor="cursor",
+ limit=1,
+ q="batch_1a2b",
+ search_type="exact",
+ status="queued",
+ tags="docs,competitor",
+ )
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncContextDev) -> None:
+ response = await async_client.batch.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = await response.parse()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncContextDev) -> None:
+ async with async_client.batch.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = await response.parse()
+ assert_matches_type(BatchListResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.cancel(
+ "batch_9f2c8a",
+ )
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncContextDev) -> None:
+ response = await async_client.batch.with_raw_response.cancel(
+ "batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = await response.parse()
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncContextDev) -> None:
+ async with async_client.batch.with_streaming_response.cancel(
+ "batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = await response.parse()
+ assert_matches_type(BatchCancelResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ await async_client.batch.with_raw_response.cancel(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_results(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.get_results(
+ batch_id="batch_9f2c8a",
+ )
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_results_with_all_params(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.get_results(
+ batch_id="batch_9f2c8a",
+ cursor="cursor",
+ limit=1,
+ )
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get_results(self, async_client: AsyncContextDev) -> None:
+ response = await async_client.batch.with_raw_response.get_results(
+ batch_id="batch_9f2c8a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = await response.parse()
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get_results(self, async_client: AsyncContextDev) -> None:
+ async with async_client.batch.with_streaming_response.get_results(
+ batch_id="batch_9f2c8a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = await response.parse()
+ assert_matches_type(BatchGetResultsResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_get_results(self, async_client: AsyncContextDev) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"):
+ await async_client.batch.with_raw_response.get_results(
+ batch_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_submit(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.submit(
+ identifiers={},
+ )
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_submit_with_all_params(self, async_client: AsyncContextDev) -> None:
+ batch = await async_client.batch.submit(
+ identifiers={"linkedin_url": "https://www.linkedin.com/in/yahia-bakour/"},
+ tags=["production", "team-alpha"],
+ timeout_ms=1000,
+ )
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_submit(self, async_client: AsyncContextDev) -> None:
+ response = await async_client.batch.with_raw_response.submit(
+ identifiers={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ batch = await response.parse()
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_submit(self, async_client: AsyncContextDev) -> None:
+ async with async_client.batch.with_streaming_response.submit(
+ identifiers={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ batch = await response.parse()
+ assert_matches_type(BatchSubmitResponse, batch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_monitors.py b/tests/api_resources/test_monitors.py
index 14ee10b..8a438f7 100644
--- a/tests/api_resources/test_monitors.py
+++ b/tests/api_resources/test_monitors.py
@@ -36,13 +36,7 @@ class TestMonitors:
@parametrize
def test_method_create(self, client: ContextDev) -> None:
monitor = client.monitors.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -54,19 +48,20 @@ def test_method_create(self, client: ContextDev) -> None:
@parametrize
def test_method_create_with_all_params(self, client: ContextDev) -> None:
monitor = client.monitors.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
+ "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.",
"normalize_whitespace": True,
},
+ change_detection={"type": "exact"},
mode="web",
+ schedule={
+ "frequency": 6,
+ "type": "interval",
+ "unit": "hours",
+ },
tags=["pricing", "competitor"],
webhook={
"url": "https://example.com/webhook",
@@ -79,13 +74,7 @@ def test_method_create_with_all_params(self, client: ContextDev) -> None:
@parametrize
def test_raw_response_create(self, client: ContextDev) -> None:
response = client.monitors.with_raw_response.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -101,13 +90,7 @@ def test_raw_response_create(self, client: ContextDev) -> None:
@parametrize
def test_streaming_response_create(self, client: ContextDev) -> None:
with client.monitors.with_streaming_response.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -188,6 +171,7 @@ def test_method_update_with_all_params(self, client: ContextDev) -> None:
target={
"type": "page",
"url": "https://acme.com/pricing",
+ "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.",
"normalize_whitespace": True,
},
webhook={
@@ -666,13 +650,7 @@ class TestAsyncMonitors:
@parametrize
async def test_method_create(self, async_client: AsyncContextDev) -> None:
monitor = await async_client.monitors.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -684,19 +662,20 @@ async def test_method_create(self, async_client: AsyncContextDev) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncContextDev) -> None:
monitor = await async_client.monitors.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
+ "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.",
"normalize_whitespace": True,
},
+ change_detection={"type": "exact"},
mode="web",
+ schedule={
+ "frequency": 6,
+ "type": "interval",
+ "unit": "hours",
+ },
tags=["pricing", "competitor"],
webhook={
"url": "https://example.com/webhook",
@@ -709,13 +688,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncContextDev
@parametrize
async def test_raw_response_create(self, async_client: AsyncContextDev) -> None:
response = await async_client.monitors.with_raw_response.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -731,13 +704,7 @@ async def test_raw_response_create(self, async_client: AsyncContextDev) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncContextDev) -> None:
async with async_client.monitors.with_streaming_response.create(
- change_detection={"type": "exact"},
name="Acme pricing page",
- schedule={
- "frequency": 6,
- "type": "interval",
- "unit": "hours",
- },
target={
"type": "page",
"url": "https://acme.com/pricing",
@@ -818,6 +785,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncContextDev
target={
"type": "page",
"url": "https://acme.com/pricing",
+ "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.",
"normalize_whitespace": True,
},
webhook={