diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9be0b05..7aa240e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/influship-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -44,7 +44,7 @@ jobs:
permissions:
contents: read
id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/influship-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -84,7 +84,7 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: ${{ github.repository == 'stainless-sdks/influship-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 1332969..3d2ac0b 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.0.1"
+ ".": "0.1.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 9671093..dbacc9c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/inf-labs/influship-api-3a7359b6f1bc29e6ba39142f71e31055407138bc92aac108c9de72dc3b0604a5.yml
-openapi_spec_hash: b5814219a0ab7727c3571b1bbfc14ce3
-config_hash: 32cf7a70e5eb35a8bead35d220e1eedb
+configured_endpoints: 27
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/inf-labs/influship-api-e5cce066c0de5c947f5e3ba65741e81a5958bd69504368fadff34a358209158f.yml
+openapi_spec_hash: 222ce3e7d51ca8ac000bd21b9f918ead
+config_hash: e6b100df61f9bb83368a29c749288c39
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..392a88e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,84 @@
+# Changelog
+
+## 0.1.0 (2026-08-25)
+
+Full Changelog: [v0.0.1...v0.1.0](https://github.com/Influship/influship-sdk-python/compare/v0.0.1...v0.1.0)
+
+### Features
+
+* **api:** add analysis_fact_count, analysis_updated_at to creator, data_updated_at to profile ([2cc74f3](https://github.com/Influship/influship-sdk-python/commit/2cc74f39f918ba5b4b3771a02687309543bbc698))
+* **api:** add audience, brand alignment, key facts, vibe fields to creator response ([4c7bf09](https://github.com/Influship/influship-sdk-python/commit/4c7bf09985211ae6cb5c9784e8c60e186cf1f58f))
+* **api:** add content_type, sort_by, upload_date params to youtube search ([bc8d3f2](https://github.com/Influship/influship-sdk-python/commit/bc8d3f21049b8c50c98789702634afa60c74f83e))
+* **api:** add creator_emails resource with lookup method ([17de04a](https://github.com/Influship/influship-sdk-python/commit/17de04a149b10c676fe01cdf338ee9af40ae1e57))
+* **api:** add creator_kinds parameter to search.create method ([712759a](https://github.com/Influship/influship-sdk-python/commit/712759ae241285079b0060ff896dc25c5cb25237))
+* **api:** add evidence/provenance fields to creator match responses ([e6a5e0b](https://github.com/Influship/influship-sdk-python/commit/e6a5e0b481569b120f0839ad73158c9f544b488a))
+* **api:** add get_post/get_transcript methods to instagram, update post types ([c5d60ec](https://github.com/Influship/influship-sdk-python/commit/c5d60ec1b410ffcb49ece5ff68299d695ba9da36))
+* **api:** add location_unverified field to search response types ([0565b01](https://github.com/Influship/influship-sdk-python/commit/0565b01e5bac87f98f458a342d9c44ed54244a83))
+* **api:** add pagination to creators lookalike, extract MatchInfo type, fix cursor passing ([3be1022](https://github.com/Influship/influship-sdk-python/commit/3be10226051e066b82f26fd99170612908ddda3a))
+* **api:** add raw resource with instagram/youtube endpoints ([d765413](https://github.com/Influship/influship-sdk-python/commit/d765413037084f7ad577d00b201a636a864c2f6b))
+* **api:** add search_id/total fields, remove cursor param from search.create ([bbd32c2](https://github.com/Influship/influship-sdk-python/commit/bbd32c2e4391a9f31543862afed49f9fa9ca3d8a))
+* **api:** add tiktok resource, increase default timeout to 3.5 minutes ([21e165f](https://github.com/Influship/influship-sdk-python/commit/21e165f300dffc9d553f60bf3e116aff75f037d4))
+* **api:** add upstream_contract_broken error to RawScraperError ([6ea6a4c](https://github.com/Influship/influship-sdk-python/commit/6ea6a4c4ed12f42b440c518d969bef48826ec2c7))
+* **api:** add upstream_unavailable error value to RawScraperError ([83b9799](https://github.com/Influship/influship-sdk-python/commit/83b9799bdf57d481fdf60324137907e4d733d20e))
+* **api:** remove source field from instagram transcript response types ([2c24026](https://github.com/Influship/influship-sdk-python/commit/2c240264f2abd9865713409ac1fb151f2479f261))
+* **api:** update instagram profile and youtube channel/transcript responses ([8b83650](https://github.com/Influship/influship-sdk-python/commit/8b83650ce59ca2f00bb2b8c0f7f8005862a1849d))
+* Fix Stainless pagination and model warnings in SDK config ([2ae3c29](https://github.com/Influship/influship-sdk-python/commit/2ae3c29275bfe74e3399f0ef0c0d950ae388531c))
+* Fix timeout enforcement and migrate Instagram scraper to curl ([c1a121d](https://github.com/Influship/influship-sdk-python/commit/c1a121d337de698699c7dac5a1fb5814648083c9))
+* honest cached /v1/posts video URLs + transcript read-through cache + discovery seeding ([72b71e3](https://github.com/Influship/influship-sdk-python/commit/72b71e36366b017314942b78436a0ea6e7fd1e24))
+* **internal/types:** support eagerly validating pydantic iterators ([252fe69](https://github.com/Influship/influship-sdk-python/commit/252fe69b3d9652bb8ff9f2bf731d63006ec5b0af))
+* **internal:** implement indices array format for query and form serialization ([b0fb922](https://github.com/Influship/influship-sdk-python/commit/b0fb922140eac09079b68be14a023d14a3f3a510))
+* Remove app-level quota system and align billing/rate-limit enforcement with Stripe + Redis ([c7d8a60](https://github.com/Influship/influship-sdk-python/commit/c7d8a6012df2e31b92554e9a8374f1b3217bbe44))
+* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([4af677d](https://github.com/Influship/influship-sdk-python/commit/4af677d5378960b14f2aaeed804b62b2c200c446))
+
+
+### Bug Fixes
+
+* **api:** make include parameter optional in creators retrieve method ([7dc834c](https://github.com/Influship/influship-sdk-python/commit/7dc834c23ef1d68df710b2de3baa996510c97b0d))
+* **api:** move RawScraperError to shared types ([061b1e2](https://github.com/Influship/influship-sdk-python/commit/061b1e27385b033eb8966ebf8a02815b7dde26e4))
+* **client:** add missing f-string prefix in file type error message ([cdd7fdb](https://github.com/Influship/influship-sdk-python/commit/cdd7fdb4104a72d2be89f7c6b48517abdb4285f4))
+* **client:** preserve hardcoded query params when merging with user params ([77f87f2](https://github.com/Influship/influship-sdk-python/commit/77f87f2cca5dbacc34e894cf2c69e3f8f208842c))
+* **deps:** bump minimum typing-extensions version ([1ebf842](https://github.com/Influship/influship-sdk-python/commit/1ebf84221a6cc9da7c4bcb414f47c4455f9b2dbe))
+* ensure file data are only sent as 1 parameter ([76674d4](https://github.com/Influship/influship-sdk-python/commit/76674d4a000df97be89864108b9bf3923b4604f3))
+* **pydantic:** do not pass `by_alias` unless set ([dba41e9](https://github.com/Influship/influship-sdk-python/commit/dba41e990d67482fc211cf0674e547c310a8eb26))
+* sanitize endpoint path params ([825e363](https://github.com/Influship/influship-sdk-python/commit/825e363c5eb9b02a3e5fc5baa994fb711ea5cdab))
+* **types:** remove model and provider from instagram transcript response types ([8944d7f](https://github.com/Influship/influship-sdk-python/commit/8944d7f405ad6e3597f397bebce7f13bc0df6fe3))
+
+
+### Performance Improvements
+
+* **client:** optimize file structure copying in multipart requests ([eca9767](https://github.com/Influship/influship-sdk-python/commit/eca97676e9a6a75faf45ea3bea0b8eefeac55b8b))
+
+
+### Chores
+
+* **ci:** skip lint on metadata-only changes ([5ee4369](https://github.com/Influship/influship-sdk-python/commit/5ee43696849a917cf7a65f66a8b298ebbc4fa2e7))
+* **ci:** skip uploading artifacts on stainless-internal branches ([2c985c8](https://github.com/Influship/influship-sdk-python/commit/2c985c8141c06115d531bc9293bb1291a3d04d45))
+* **internal:** add request options to SSE classes ([fa680d0](https://github.com/Influship/influship-sdk-python/commit/fa680d005639f7836e614b03c165791f1c64c0b3))
+* **internal:** codegen related update ([6ed3406](https://github.com/Influship/influship-sdk-python/commit/6ed3406334c705ae2278a09a040b916c0432d9ae))
+* **internal:** codegen related update ([a7d0c03](https://github.com/Influship/influship-sdk-python/commit/a7d0c03f03bae935f64b076c211d283ad9069495))
+* **internal:** make `test_proxy_environment_variables` more resilient ([1775e30](https://github.com/Influship/influship-sdk-python/commit/1775e30216baabb1affe64be26b9455a35bc7a30))
+* **internal:** make `test_proxy_environment_variables` more resilient to env ([2042983](https://github.com/Influship/influship-sdk-python/commit/20429833fae8ecd9dbe6eee1d140cdb9e0763260))
+* **internal:** more robust bootstrap script ([09db9ca](https://github.com/Influship/influship-sdk-python/commit/09db9caf00714de6ccb7eccd314c0a903b6ea55f))
+* **internal:** refactor Instagram response types to shared models, fix address_json type ([1ce31a9](https://github.com/Influship/influship-sdk-python/commit/1ce31a9406b653b08e333139febfb5acb01ca641))
+* **internal:** reformat pyproject.toml ([c63d362](https://github.com/Influship/influship-sdk-python/commit/c63d362a824ea0145bb90d8be4e493463d32028c))
+* **internal:** regenerate SDK with no functional changes ([f8a1efa](https://github.com/Influship/influship-sdk-python/commit/f8a1efad554f13a85b4bf49f47f4daa0190da858))
+* **internal:** regenerate SDK with no functional changes ([2f56132](https://github.com/Influship/influship-sdk-python/commit/2f561321662c817f965ff9d4ad61e3b13f15de38))
+* **internal:** tweak CI branches ([5eea6ab](https://github.com/Influship/influship-sdk-python/commit/5eea6ab384a360852d54e5617e92e0d8d090a12e))
+* **internal:** update gitignore ([a47bc6c](https://github.com/Influship/influship-sdk-python/commit/a47bc6c615c786a620fad78eafb31927cbecc718))
+* **tests:** update region parameter in tiktok test fixtures ([d4fbafa](https://github.com/Influship/influship-sdk-python/commit/d4fbafac7dae38bd60248d5dfe3cc7850295ead3))
+* update Stainless production repos to influship org ([f92ef39](https://github.com/Influship/influship-sdk-python/commit/f92ef39428d669160509c279e52deb10343e2b2c))
+
+
+### Documentation
+
+* **api:** add MCP tool references to creators/posts/profiles/search methods ([fb75a8e](https://github.com/Influship/influship-sdk-python/commit/fb75a8eedb6c1e86edb35b94e82d0eb27b9f4537))
+* **api:** clarify handle parameter format in youtube resource ([ef29fd6](https://github.com/Influship/influship-sdk-python/commit/ef29fd64380a379c0589b4bec19996ae85da389a))
+* **api:** clarify top_engagement calculation and pagination in posts list ([d894067](https://github.com/Influship/influship-sdk-python/commit/d894067fb7587dd74c15cb6cc8b334933bdf6bee))
+* **api:** clarify transient upstream recovery in instagram batch post lookup ([ef30fdf](https://github.com/Influship/influship-sdk-python/commit/ef30fdf93c710133f13a7e5e2cf04c2bf89e2584))
+* **api:** correct profile to channel terminology in youtube channel method ([a8f561f](https://github.com/Influship/influship-sdk-python/commit/a8f561fde0da698d26689d699c394cbcb3475f8d))
+* **api:** update evidence field documentation in match_info ([0f95b69](https://github.com/Influship/influship-sdk-python/commit/0f95b698268b32586b8d14edcb09685c0ba027be))
+* **api:** update instagram method docstrings ([9d881aa](https://github.com/Influship/influship-sdk-python/commit/9d881aadfbbc7d742d739042a96c973a9824573f))
+* **api:** update pricing and limits documentation in instagram resource ([0b64f9c](https://github.com/Influship/influship-sdk-python/commit/0b64f9c09ff15d8c73309855f065aae49e1cb870))
+* **api:** update pricing documentation across creators/posts/profiles/raw/search ([b82801e](https://github.com/Influship/influship-sdk-python/commit/b82801e0c5cc55dc0509e3a8ac412518ca9745ce))
+* **internal:** update MCP server package name in installation links ([7d78559](https://github.com/Influship/influship-sdk-python/commit/7d78559d721175e453eea56d8fab60a0f4c9a229))
+* update examples ([53aa0d6](https://github.com/Influship/influship-sdk-python/commit/53aa0d622795bdc2076f69d67ac51525b814f7d6))
diff --git a/README.md b/README.md
index 14d8637..8d76d17 100644
--- a/README.md
+++ b/README.md
@@ -296,7 +296,7 @@ client.with_options(max_retries=5).search.create(
### Timeouts
-By default requests time out after 1 minute. You can configure this with a `timeout` option,
+By default requests time out after 3.5 minutes. You can configure this with a `timeout` option,
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
```python
@@ -304,7 +304,7 @@ from influship import Influship
# Configure the default for all requests:
client = Influship(
- # 20 seconds (default is 1 minute)
+ # 20 seconds (default is 3.5 minutes)
timeout=20.0,
)
diff --git a/api.md b/api.md
index b7922bf..c2c2eac 100644
--- a/api.md
+++ b/api.md
@@ -1,7 +1,7 @@
# Shared Types
```python
-from influship.types import CreatorBasic, ProfileSummary
+from influship.types import CreatorBasic, ProfileSummary, RawScraperError
```
# Health
@@ -69,6 +69,18 @@ Methods:
- client.profiles.get(username, \*, platform) -> ProfileGetResponse
- client.profiles.lookup(\*\*params) -> ProfileLookupResponse
+# CreatorEmails
+
+Types:
+
+```python
+from influship.types import CreatorEmailLookupResponse
+```
+
+Methods:
+
+- client.creator_emails.lookup(\*\*params) -> CreatorEmailLookupResponse
+
# Posts
Types:
@@ -91,7 +103,6 @@ Types:
from influship.types.raw import (
InstagramSinglePostResponse,
InstagramTranscriptResponse,
- RawScraperError,
InstagramGetPostResponse,
InstagramGetPostsResponse,
InstagramGetProfileResponse,
@@ -118,7 +129,9 @@ from influship.types.raw import (
YoutubeGetChannelResponse,
YoutubeGetChannelTranscriptsResponse,
YoutubeGetTranscriptResponse,
+ YoutubeGetVideoResponse,
YoutubeSearchResponse,
+ YoutubeTypeaheadResponse,
)
```
@@ -127,4 +140,31 @@ Methods:
- client.raw.youtube.get_channel(handle, \*\*params) -> YoutubeGetChannelResponse
- client.raw.youtube.get_channel_transcripts(handle, \*\*params) -> YoutubeGetChannelTranscriptsResponse
- client.raw.youtube.get_transcript(video_id, \*\*params) -> YoutubeGetTranscriptResponse
+- client.raw.youtube.get_video(video_id) -> YoutubeGetVideoResponse
- client.raw.youtube.search(\*\*params) -> YoutubeSearchResponse
+- client.raw.youtube.typeahead(\*\*params) -> YoutubeTypeaheadResponse
+
+## Tiktok
+
+Types:
+
+```python
+from influship.types.raw import (
+ Profile,
+ Transcript,
+ Video,
+ TiktokGetProfileResponse,
+ TiktokGetVideoResponse,
+ TiktokGetVideoTranscriptResponse,
+ TiktokListProfileVideosResponse,
+ TiktokListVideoCommentsResponse,
+)
+```
+
+Methods:
+
+- client.raw.tiktok.get_profile(username) -> TiktokGetProfileResponse
+- client.raw.tiktok.get_video(\*\*params) -> TiktokGetVideoResponse
+- client.raw.tiktok.get_video_transcript(\*\*params) -> TiktokGetVideoTranscriptResponse
+- client.raw.tiktok.list_profile_videos(username, \*\*params) -> TiktokListProfileVideosResponse
+- client.raw.tiktok.list_video_comments(\*\*params) -> TiktokListVideoCommentsResponse
diff --git a/pyproject.toml b/pyproject.toml
index 0355ae6..b23b700 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "influship"
-version = "0.0.1"
+version = "0.1.0"
description = "The official Python library for the Influship API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/influship/_client.py b/src/influship/_client.py
index 55a6663..40c00f2 100644
--- a/src/influship/_client.py
+++ b/src/influship/_client.py
@@ -35,13 +35,14 @@
)
if TYPE_CHECKING:
- from .resources import raw, posts, health, search, creators, profiles
+ from .resources import raw, posts, health, search, creators, profiles, creator_emails
from .resources.posts import PostsResource, AsyncPostsResource
from .resources.health import HealthResource, AsyncHealthResource
from .resources.search import SearchResource, AsyncSearchResource
from .resources.raw.raw import RawResource, AsyncRawResource
from .resources.creators import CreatorsResource, AsyncCreatorsResource
from .resources.profiles import ProfilesResource, AsyncProfilesResource
+ from .resources.creator_emails import CreatorEmailsResource, AsyncCreatorEmailsResource
__all__ = [
"Timeout",
@@ -154,6 +155,16 @@ def profiles(self) -> ProfilesResource:
return ProfilesResource(self)
+ @cached_property
+ def creator_emails(self) -> CreatorEmailsResource:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import CreatorEmailsResource
+
+ return CreatorEmailsResource(self)
+
@cached_property
def posts(self) -> PostsResource:
"""
@@ -381,6 +392,16 @@ def profiles(self) -> AsyncProfilesResource:
return AsyncProfilesResource(self)
+ @cached_property
+ def creator_emails(self) -> AsyncCreatorEmailsResource:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import AsyncCreatorEmailsResource
+
+ return AsyncCreatorEmailsResource(self)
+
@cached_property
def posts(self) -> AsyncPostsResource:
"""
@@ -550,6 +571,16 @@ def profiles(self) -> profiles.ProfilesResourceWithRawResponse:
return ProfilesResourceWithRawResponse(self._client.profiles)
+ @cached_property
+ def creator_emails(self) -> creator_emails.CreatorEmailsResourceWithRawResponse:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import CreatorEmailsResourceWithRawResponse
+
+ return CreatorEmailsResourceWithRawResponse(self._client.creator_emails)
+
@cached_property
def posts(self) -> posts.PostsResourceWithRawResponse:
"""
@@ -607,6 +638,16 @@ def profiles(self) -> profiles.AsyncProfilesResourceWithRawResponse:
return AsyncProfilesResourceWithRawResponse(self._client.profiles)
+ @cached_property
+ def creator_emails(self) -> creator_emails.AsyncCreatorEmailsResourceWithRawResponse:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import AsyncCreatorEmailsResourceWithRawResponse
+
+ return AsyncCreatorEmailsResourceWithRawResponse(self._client.creator_emails)
+
@cached_property
def posts(self) -> posts.AsyncPostsResourceWithRawResponse:
"""
@@ -664,6 +705,16 @@ def profiles(self) -> profiles.ProfilesResourceWithStreamingResponse:
return ProfilesResourceWithStreamingResponse(self._client.profiles)
+ @cached_property
+ def creator_emails(self) -> creator_emails.CreatorEmailsResourceWithStreamingResponse:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import CreatorEmailsResourceWithStreamingResponse
+
+ return CreatorEmailsResourceWithStreamingResponse(self._client.creator_emails)
+
@cached_property
def posts(self) -> posts.PostsResourceWithStreamingResponse:
"""
@@ -721,6 +772,16 @@ def profiles(self) -> profiles.AsyncProfilesResourceWithStreamingResponse:
return AsyncProfilesResourceWithStreamingResponse(self._client.profiles)
+ @cached_property
+ def creator_emails(self) -> creator_emails.AsyncCreatorEmailsResourceWithStreamingResponse:
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+ from .resources.creator_emails import AsyncCreatorEmailsResourceWithStreamingResponse
+
+ return AsyncCreatorEmailsResourceWithStreamingResponse(self._client.creator_emails)
+
@cached_property
def posts(self) -> posts.AsyncPostsResourceWithStreamingResponse:
"""
diff --git a/src/influship/_constants.py b/src/influship/_constants.py
index 6ddf2c7..2e2abdd 100644
--- a/src/influship/_constants.py
+++ b/src/influship/_constants.py
@@ -5,8 +5,8 @@
RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
-# default timeout is 1 minute
-DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
+# default timeout is 3.5 minutes
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=210, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
diff --git a/src/influship/_version.py b/src/influship/_version.py
index de0c280..388cde9 100644
--- a/src/influship/_version.py
+++ b/src/influship/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "influship"
-__version__ = "0.0.1" # x-release-please-version
+__version__ = "0.1.0" # x-release-please-version
diff --git a/src/influship/resources/__init__.py b/src/influship/resources/__init__.py
index c54bbd6..abeb1ca 100644
--- a/src/influship/resources/__init__.py
+++ b/src/influship/resources/__init__.py
@@ -48,6 +48,14 @@
ProfilesResourceWithStreamingResponse,
AsyncProfilesResourceWithStreamingResponse,
)
+from .creator_emails import (
+ CreatorEmailsResource,
+ AsyncCreatorEmailsResource,
+ CreatorEmailsResourceWithRawResponse,
+ AsyncCreatorEmailsResourceWithRawResponse,
+ CreatorEmailsResourceWithStreamingResponse,
+ AsyncCreatorEmailsResourceWithStreamingResponse,
+)
__all__ = [
"HealthResource",
@@ -74,6 +82,12 @@
"AsyncProfilesResourceWithRawResponse",
"ProfilesResourceWithStreamingResponse",
"AsyncProfilesResourceWithStreamingResponse",
+ "CreatorEmailsResource",
+ "AsyncCreatorEmailsResource",
+ "CreatorEmailsResourceWithRawResponse",
+ "AsyncCreatorEmailsResourceWithRawResponse",
+ "CreatorEmailsResourceWithStreamingResponse",
+ "AsyncCreatorEmailsResourceWithStreamingResponse",
"PostsResource",
"AsyncPostsResource",
"PostsResourceWithRawResponse",
diff --git a/src/influship/resources/creator_emails.py b/src/influship/resources/creator_emails.py
new file mode 100644
index 0000000..061bb9f
--- /dev/null
+++ b/src/influship/resources/creator_emails.py
@@ -0,0 +1,197 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+
+import httpx
+
+from ..types import creator_email_lookup_params
+from .._types import Body, Query, Headers, NotGiven, not_given
+from .._utils import 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.creator_email_lookup_response import CreatorEmailLookupResponse
+
+__all__ = ["CreatorEmailsResource", "AsyncCreatorEmailsResource"]
+
+
+class CreatorEmailsResource(SyncAPIResource):
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> CreatorEmailsResourceWithRawResponse:
+ """
+ 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/Influship/influship-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return CreatorEmailsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CreatorEmailsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Influship/influship-sdk-python#with_streaming_response
+ """
+ return CreatorEmailsResourceWithStreamingResponse(self)
+
+ def lookup(
+ self,
+ *,
+ creators: Iterable[creator_email_lookup_params.Creator],
+ # 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,
+ ) -> CreatorEmailLookupResponse:
+ """
+ Look up known email addresses for creators by creator ID or social username.
+
+ **Billing behavior:**
+
+ - Charged only for unique resolved creators with at least one returned email
+ - Empty and unresolved results are not billable
+ - Returns validation status so unvalidated emails are explicit
+
+ **Pricing**: 5 credits per creator with at least one returned email ($0.05)
+
+ Args:
+ creators: Creator lookups to resolve. Response rows preserve this input order.
+
+ 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(
+ "/v1/creator-emails/lookup",
+ body=maybe_transform({"creators": creators}, creator_email_lookup_params.CreatorEmailLookupParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatorEmailLookupResponse,
+ )
+
+
+class AsyncCreatorEmailsResource(AsyncAPIResource):
+ """Look up known creator email addresses by creator ID or social username.
+
+ Empty or unresolved results are not billable.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncCreatorEmailsResourceWithRawResponse:
+ """
+ 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/Influship/influship-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncCreatorEmailsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCreatorEmailsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Influship/influship-sdk-python#with_streaming_response
+ """
+ return AsyncCreatorEmailsResourceWithStreamingResponse(self)
+
+ async def lookup(
+ self,
+ *,
+ creators: Iterable[creator_email_lookup_params.Creator],
+ # 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,
+ ) -> CreatorEmailLookupResponse:
+ """
+ Look up known email addresses for creators by creator ID or social username.
+
+ **Billing behavior:**
+
+ - Charged only for unique resolved creators with at least one returned email
+ - Empty and unresolved results are not billable
+ - Returns validation status so unvalidated emails are explicit
+
+ **Pricing**: 5 credits per creator with at least one returned email ($0.05)
+
+ Args:
+ creators: Creator lookups to resolve. Response rows preserve this input order.
+
+ 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(
+ "/v1/creator-emails/lookup",
+ body=await async_maybe_transform(
+ {"creators": creators}, creator_email_lookup_params.CreatorEmailLookupParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatorEmailLookupResponse,
+ )
+
+
+class CreatorEmailsResourceWithRawResponse:
+ def __init__(self, creator_emails: CreatorEmailsResource) -> None:
+ self._creator_emails = creator_emails
+
+ self.lookup = to_raw_response_wrapper(
+ creator_emails.lookup,
+ )
+
+
+class AsyncCreatorEmailsResourceWithRawResponse:
+ def __init__(self, creator_emails: AsyncCreatorEmailsResource) -> None:
+ self._creator_emails = creator_emails
+
+ self.lookup = async_to_raw_response_wrapper(
+ creator_emails.lookup,
+ )
+
+
+class CreatorEmailsResourceWithStreamingResponse:
+ def __init__(self, creator_emails: CreatorEmailsResource) -> None:
+ self._creator_emails = creator_emails
+
+ self.lookup = to_streamed_response_wrapper(
+ creator_emails.lookup,
+ )
+
+
+class AsyncCreatorEmailsResourceWithStreamingResponse:
+ def __init__(self, creator_emails: AsyncCreatorEmailsResource) -> None:
+ self._creator_emails = creator_emails
+
+ self.lookup = async_to_streamed_response_wrapper(
+ creator_emails.lookup,
+ )
diff --git a/src/influship/resources/posts.py b/src/influship/resources/posts.py
index 0813ecd..4beaee0 100644
--- a/src/influship/resources/posts.py
+++ b/src/influship/resources/posts.py
@@ -75,11 +75,15 @@ def list(
**Sort options:**
- `recent`: Most recent posts first (default)
- - `top_engagement`: Highest engagement rate first
+ - `top_engagement`: Highest view-based engagement rate first, calculated as
+ (likes + comments) / views. Posts without measurable views sort last
- `most_likes`: Most likes first
- `most_views`: Most views first (video content)
- `most_comments`: Most comments first
+ Pagination uses an opaque keyset cursor. Pass `next_cursor` back with the same
+ `sort` value; cursors cannot be reused across sort orders.
+
Also callable as the `get_posts` MCP tool — see
[the MCP server guide](/guides/mcp-server) for setup.
@@ -88,7 +92,8 @@ def list(
Args:
creator_id: Creator ID (use this OR platform+username)
- cursor: Pagination cursor for next page
+ cursor: Opaque keyset cursor from next_cursor. A cursor is bound to the sort order that
+ produced it; using it with another sort returns 400.
limit: Maximum posts to return
@@ -181,11 +186,15 @@ def list(
**Sort options:**
- `recent`: Most recent posts first (default)
- - `top_engagement`: Highest engagement rate first
+ - `top_engagement`: Highest view-based engagement rate first, calculated as
+ (likes + comments) / views. Posts without measurable views sort last
- `most_likes`: Most likes first
- `most_views`: Most views first (video content)
- `most_comments`: Most comments first
+ Pagination uses an opaque keyset cursor. Pass `next_cursor` back with the same
+ `sort` value; cursors cannot be reused across sort orders.
+
Also callable as the `get_posts` MCP tool — see
[the MCP server guide](/guides/mcp-server) for setup.
@@ -194,7 +203,8 @@ def list(
Args:
creator_id: Creator ID (use this OR platform+username)
- cursor: Pagination cursor for next page
+ cursor: Opaque keyset cursor from next_cursor. A cursor is bound to the sort order that
+ produced it; using it with another sort returns 400.
limit: Maximum posts to return
diff --git a/src/influship/resources/raw/__init__.py b/src/influship/resources/raw/__init__.py
index 6840f27..205a541 100644
--- a/src/influship/resources/raw/__init__.py
+++ b/src/influship/resources/raw/__init__.py
@@ -8,6 +8,14 @@
RawResourceWithStreamingResponse,
AsyncRawResourceWithStreamingResponse,
)
+from .tiktok import (
+ TiktokResource,
+ AsyncTiktokResource,
+ TiktokResourceWithRawResponse,
+ AsyncTiktokResourceWithRawResponse,
+ TiktokResourceWithStreamingResponse,
+ AsyncTiktokResourceWithStreamingResponse,
+)
from .youtube import (
YoutubeResource,
AsyncYoutubeResource,
@@ -38,6 +46,12 @@
"AsyncYoutubeResourceWithRawResponse",
"YoutubeResourceWithStreamingResponse",
"AsyncYoutubeResourceWithStreamingResponse",
+ "TiktokResource",
+ "AsyncTiktokResource",
+ "TiktokResourceWithRawResponse",
+ "AsyncTiktokResourceWithRawResponse",
+ "TiktokResourceWithStreamingResponse",
+ "AsyncTiktokResourceWithStreamingResponse",
"RawResource",
"AsyncRawResource",
"RawResourceWithRawResponse",
diff --git a/src/influship/resources/raw/instagram.py b/src/influship/resources/raw/instagram.py
index decb1e2..d77efc8 100644
--- a/src/influship/resources/raw/instagram.py
+++ b/src/influship/resources/raw/instagram.py
@@ -73,8 +73,12 @@ def get_post(
product mentions, music attribution, location, display resources, and video
versions.
- **Note:** These fields are only guaranteed on this raw single-post lookup for
- now. Regular cached post-list endpoints may not include them yet.
+ **Note:** These fields are only guaranteed on this raw single-post lookup.
+ Cached post-list endpoints may not include them.
+
+ Returns fresh `video_url` (single best stream) and `video_versions[]`
+ (multi-bitrate). These are signed Instagram CDN URLs valid for ~24h — download
+ promptly. For carousels with embedded videos, see `carousel_items[].video_url`.
**Pricing**: 1 credit per post scraped ($0.01)
@@ -114,9 +118,15 @@ def get_posts(
This
returns one item per requested shortcode with per-item success or error details.
+ Transient upstream recovery is handled automatically within the request
+ deadline.
+
+ **Note:** Batch post lookup is capped at 20 shortcodes per request. API-key and
+ OAuth calls are charged only for successful items; x402 and MPP use the
+ advertised request price.
- **Note:** Batch post lookup is capped at 20 shortcodes per request and is
- charged for every requested shortcode.
+ Returns fresh `video_url` and `video_versions[]` per post (signed IG CDN URLs,
+ ~24h validity). Batch up to 20 posts at 1 credit ($0.01) each.
**Pricing**: 1 credit per post scraped ($0.01)
@@ -167,6 +177,13 @@ def get_profile(
**Note:** Live scraping is slower than cached data (2-5 seconds) and costs more.
Use cached endpoints when freshness isn't critical.
+ The profile response returns an empty `posts[]` array unless
+ `include_posts=true`. When posts are included, `post_limit` accepts 1-50 and
+ defaults to 12. The array contains the recent posts available in Instagram's
+ source response, up to that limit. Video posts include a fresh `video_url`. This
+ is the cheapest bulk-download path: 0.5 credits ($0.005) per profile call vs 1
+ credit per individual raw-post call.
+
**Pricing**: 0.5 credits per profile scraped ($0.005)
Args:
@@ -218,12 +235,7 @@ def get_transcript(
) -> InstagramGetTranscriptResponse:
"""
Transcribe an Instagram video post by shortcode and return the raw post-page
- data used for transcription. For now this raw endpoint retranscribes every
- request and piggybacks the post plus transcript into our database when the owner
- account exists.
-
- **Note:** Cached transcript reads are a planned follow-up; public pricing stays
- the same for live and cached transcript delivery.
+ data used for transcription.
**Pricing**: 5 credits per transcript ($0.05)
@@ -273,10 +285,9 @@ def get_transcripts(
item per requested shortcode with per-item success or error details. Successful
items include the raw post-page data used for transcription.
- **Note:** Batch transcription is capped at 10 shortcodes per request,
- retranscribes every request for now, and is charged for every requested
- shortcode. Cached transcript reads are a planned follow-up; public pricing stays
- the same for live and cached transcript delivery.
+ **Note:** Batch transcription is capped at 10 shortcodes per request. API-key
+ and OAuth calls are charged only for successful items; x402 and MPP use the
+ advertised request price.
**Pricing**: 5 credits per transcript ($0.05)
@@ -352,8 +363,12 @@ async def get_post(
product mentions, music attribution, location, display resources, and video
versions.
- **Note:** These fields are only guaranteed on this raw single-post lookup for
- now. Regular cached post-list endpoints may not include them yet.
+ **Note:** These fields are only guaranteed on this raw single-post lookup.
+ Cached post-list endpoints may not include them.
+
+ Returns fresh `video_url` (single best stream) and `video_versions[]`
+ (multi-bitrate). These are signed Instagram CDN URLs valid for ~24h — download
+ promptly. For carousels with embedded videos, see `carousel_items[].video_url`.
**Pricing**: 1 credit per post scraped ($0.01)
@@ -393,9 +408,15 @@ async def get_posts(
This
returns one item per requested shortcode with per-item success or error details.
+ Transient upstream recovery is handled automatically within the request
+ deadline.
+
+ **Note:** Batch post lookup is capped at 20 shortcodes per request. API-key and
+ OAuth calls are charged only for successful items; x402 and MPP use the
+ advertised request price.
- **Note:** Batch post lookup is capped at 20 shortcodes per request and is
- charged for every requested shortcode.
+ Returns fresh `video_url` and `video_versions[]` per post (signed IG CDN URLs,
+ ~24h validity). Batch up to 20 posts at 1 credit ($0.01) each.
**Pricing**: 1 credit per post scraped ($0.01)
@@ -448,6 +469,13 @@ async def get_profile(
**Note:** Live scraping is slower than cached data (2-5 seconds) and costs more.
Use cached endpoints when freshness isn't critical.
+ The profile response returns an empty `posts[]` array unless
+ `include_posts=true`. When posts are included, `post_limit` accepts 1-50 and
+ defaults to 12. The array contains the recent posts available in Instagram's
+ source response, up to that limit. Video posts include a fresh `video_url`. This
+ is the cheapest bulk-download path: 0.5 credits ($0.005) per profile call vs 1
+ credit per individual raw-post call.
+
**Pricing**: 0.5 credits per profile scraped ($0.005)
Args:
@@ -499,12 +527,7 @@ async def get_transcript(
) -> InstagramGetTranscriptResponse:
"""
Transcribe an Instagram video post by shortcode and return the raw post-page
- data used for transcription. For now this raw endpoint retranscribes every
- request and piggybacks the post plus transcript into our database when the owner
- account exists.
-
- **Note:** Cached transcript reads are a planned follow-up; public pricing stays
- the same for live and cached transcript delivery.
+ data used for transcription.
**Pricing**: 5 credits per transcript ($0.05)
@@ -554,10 +577,9 @@ async def get_transcripts(
item per requested shortcode with per-item success or error details. Successful
items include the raw post-page data used for transcription.
- **Note:** Batch transcription is capped at 10 shortcodes per request,
- retranscribes every request for now, and is charged for every requested
- shortcode. Cached transcript reads are a planned follow-up; public pricing stays
- the same for live and cached transcript delivery.
+ **Note:** Batch transcription is capped at 10 shortcodes per request. API-key
+ and OAuth calls are charged only for successful items; x402 and MPP use the
+ advertised request price.
**Pricing**: 5 credits per transcript ($0.05)
diff --git a/src/influship/resources/raw/raw.py b/src/influship/resources/raw/raw.py
index 4572650..aab68ae 100644
--- a/src/influship/resources/raw/raw.py
+++ b/src/influship/resources/raw/raw.py
@@ -2,6 +2,14 @@
from __future__ import annotations
+from .tiktok import (
+ TiktokResource,
+ AsyncTiktokResource,
+ TiktokResourceWithRawResponse,
+ AsyncTiktokResourceWithRawResponse,
+ TiktokResourceWithStreamingResponse,
+ AsyncTiktokResourceWithStreamingResponse,
+)
from .youtube import (
YoutubeResource,
AsyncYoutubeResource,
@@ -41,6 +49,14 @@ def youtube(self) -> YoutubeResource:
"""
return YoutubeResource(self._client)
+ @cached_property
+ def tiktok(self) -> TiktokResource:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return TiktokResource(self._client)
+
@cached_property
def with_raw_response(self) -> RawResourceWithRawResponse:
"""
@@ -78,6 +94,14 @@ def youtube(self) -> AsyncYoutubeResource:
"""
return AsyncYoutubeResource(self._client)
+ @cached_property
+ def tiktok(self) -> AsyncTiktokResource:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return AsyncTiktokResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncRawResourceWithRawResponse:
"""
@@ -118,6 +142,14 @@ def youtube(self) -> YoutubeResourceWithRawResponse:
"""
return YoutubeResourceWithRawResponse(self._raw.youtube)
+ @cached_property
+ def tiktok(self) -> TiktokResourceWithRawResponse:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return TiktokResourceWithRawResponse(self._raw.tiktok)
+
class AsyncRawResourceWithRawResponse:
def __init__(self, raw: AsyncRawResource) -> None:
@@ -139,6 +171,14 @@ def youtube(self) -> AsyncYoutubeResourceWithRawResponse:
"""
return AsyncYoutubeResourceWithRawResponse(self._raw.youtube)
+ @cached_property
+ def tiktok(self) -> AsyncTiktokResourceWithRawResponse:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return AsyncTiktokResourceWithRawResponse(self._raw.tiktok)
+
class RawResourceWithStreamingResponse:
def __init__(self, raw: RawResource) -> None:
@@ -160,6 +200,14 @@ def youtube(self) -> YoutubeResourceWithStreamingResponse:
"""
return YoutubeResourceWithStreamingResponse(self._raw.youtube)
+ @cached_property
+ def tiktok(self) -> TiktokResourceWithStreamingResponse:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return TiktokResourceWithStreamingResponse(self._raw.tiktok)
+
class AsyncRawResourceWithStreamingResponse:
def __init__(self, raw: AsyncRawResource) -> None:
@@ -180,3 +228,11 @@ def youtube(self) -> AsyncYoutubeResourceWithStreamingResponse:
Use when you need the most current information or data for profiles not yet in our database.
"""
return AsyncYoutubeResourceWithStreamingResponse(self._raw.youtube)
+
+ @cached_property
+ def tiktok(self) -> AsyncTiktokResourceWithStreamingResponse:
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+ return AsyncTiktokResourceWithStreamingResponse(self._raw.tiktok)
diff --git a/src/influship/resources/raw/tiktok.py b/src/influship/resources/raw/tiktok.py
new file mode 100644
index 0000000..a62fa2c
--- /dev/null
+++ b/src/influship/resources/raw/tiktok.py
@@ -0,0 +1,638 @@
+# 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 Body, Omit, Query, Headers, NotGiven, 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 ...types.raw import (
+ tiktok_get_video_params,
+ tiktok_list_profile_videos_params,
+ tiktok_list_video_comments_params,
+ tiktok_get_video_transcript_params,
+)
+from ..._base_client import make_request_options
+from ...types.raw.tiktok_get_video_response import TiktokGetVideoResponse
+from ...types.raw.tiktok_get_profile_response import TiktokGetProfileResponse
+from ...types.raw.tiktok_list_profile_videos_response import TiktokListProfileVideosResponse
+from ...types.raw.tiktok_list_video_comments_response import TiktokListVideoCommentsResponse
+from ...types.raw.tiktok_get_video_transcript_response import TiktokGetVideoTranscriptResponse
+
+__all__ = ["TiktokResource", "AsyncTiktokResource"]
+
+
+class TiktokResource(SyncAPIResource):
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> TiktokResourceWithRawResponse:
+ """
+ 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/Influship/influship-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return TiktokResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> TiktokResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Influship/influship-sdk-python#with_streaming_response
+ """
+ return TiktokResourceWithStreamingResponse(self)
+
+ def get_profile(
+ self,
+ username: 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,
+ ) -> TiktokGetProfileResponse:
+ """
+ Fetch a normalized TikTok profile with current identity, biography,
+ verification, and audience metrics.
+
+ **Pricing**: 0.5 credits per profile scraped ($0.005)
+
+ Args:
+ username: TikTok username, with or without a leading @
+
+ 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 username:
+ raise ValueError(f"Expected a non-empty value for `username` but received {username!r}")
+ return self._get(
+ path_template("/v1/raw/tiktok/profile/{username}", username=username),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TiktokGetProfileResponse,
+ )
+
+ def get_video(
+ self,
+ *,
+ url: str,
+ region: 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,
+ ) -> TiktokGetVideoResponse:
+ """
+ Fetch normalized details and current engagement metrics for a TikTok video URL.
+ Signed media URLs are temporary and should be downloaded promptly.
+
+ **Pricing**: 0.5 credits per video scraped ($0.005)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ region: Two-letter region code
+
+ 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(
+ "/v1/raw/tiktok/video",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "url": url,
+ "region": region,
+ },
+ tiktok_get_video_params.TiktokGetVideoParams,
+ ),
+ ),
+ cast_to=TiktokGetVideoResponse,
+ )
+
+ def get_video_transcript(
+ self,
+ *,
+ url: 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,
+ ) -> TiktokGetVideoTranscriptResponse:
+ """
+ Fetch or generate a normalized TikTok transcript with plain text and timestamped
+ segments. The detected-language transcript is reused on later requests.
+
+ **Pricing**: 5 credits per transcript ($0.05)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ 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(
+ "/v1/raw/tiktok/video/transcript",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"url": url}, tiktok_get_video_transcript_params.TiktokGetVideoTranscriptParams),
+ ),
+ cast_to=TiktokGetVideoTranscriptResponse,
+ )
+
+ def list_profile_videos(
+ self,
+ username: str,
+ *,
+ cursor: str | Omit = omit,
+ region: str | Omit = omit,
+ sort_by: Literal["latest", "popular"] | 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,
+ ) -> TiktokListProfileVideosResponse:
+ """Fetch one cursor-paginated page of normalized TikTok videos.
+
+ Signed media URLs
+ are temporary and should be downloaded promptly.
+
+ **Pricing**: 0.5 credits per video page scraped ($0.005)
+
+ Args:
+ username: TikTok username, with or without a leading @
+
+ cursor: Opaque cursor from the previous response
+
+ region: Two-letter region code
+
+ sort_by: Video ordering
+
+ 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 username:
+ raise ValueError(f"Expected a non-empty value for `username` but received {username!r}")
+ return self._get(
+ path_template("/v1/raw/tiktok/profile/{username}/videos", username=username),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "region": region,
+ "sort_by": sort_by,
+ },
+ tiktok_list_profile_videos_params.TiktokListProfileVideosParams,
+ ),
+ ),
+ cast_to=TiktokListProfileVideosResponse,
+ )
+
+ def list_video_comments(
+ self,
+ *,
+ url: str,
+ cursor: 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,
+ ) -> TiktokListVideoCommentsResponse:
+ """
+ Fetch one cursor-paginated page of normalized comments for a TikTok video URL.
+
+ **Pricing**: 0.5 credits per comment page scraped ($0.005)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ cursor: Opaque cursor from the previous response
+
+ 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(
+ "/v1/raw/tiktok/video/comments",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "url": url,
+ "cursor": cursor,
+ },
+ tiktok_list_video_comments_params.TiktokListVideoCommentsParams,
+ ),
+ ),
+ cast_to=TiktokListVideoCommentsResponse,
+ )
+
+
+class AsyncTiktokResource(AsyncAPIResource):
+ """Fetch fresh data directly from social platforms in real-time.
+
+ Use when you need the most current information or data for profiles not yet in our database.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncTiktokResourceWithRawResponse:
+ """
+ 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/Influship/influship-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncTiktokResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncTiktokResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Influship/influship-sdk-python#with_streaming_response
+ """
+ return AsyncTiktokResourceWithStreamingResponse(self)
+
+ async def get_profile(
+ self,
+ username: 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,
+ ) -> TiktokGetProfileResponse:
+ """
+ Fetch a normalized TikTok profile with current identity, biography,
+ verification, and audience metrics.
+
+ **Pricing**: 0.5 credits per profile scraped ($0.005)
+
+ Args:
+ username: TikTok username, with or without a leading @
+
+ 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 username:
+ raise ValueError(f"Expected a non-empty value for `username` but received {username!r}")
+ return await self._get(
+ path_template("/v1/raw/tiktok/profile/{username}", username=username),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TiktokGetProfileResponse,
+ )
+
+ async def get_video(
+ self,
+ *,
+ url: str,
+ region: 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,
+ ) -> TiktokGetVideoResponse:
+ """
+ Fetch normalized details and current engagement metrics for a TikTok video URL.
+ Signed media URLs are temporary and should be downloaded promptly.
+
+ **Pricing**: 0.5 credits per video scraped ($0.005)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ region: Two-letter region code
+
+ 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(
+ "/v1/raw/tiktok/video",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "url": url,
+ "region": region,
+ },
+ tiktok_get_video_params.TiktokGetVideoParams,
+ ),
+ ),
+ cast_to=TiktokGetVideoResponse,
+ )
+
+ async def get_video_transcript(
+ self,
+ *,
+ url: 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,
+ ) -> TiktokGetVideoTranscriptResponse:
+ """
+ Fetch or generate a normalized TikTok transcript with plain text and timestamped
+ segments. The detected-language transcript is reused on later requests.
+
+ **Pricing**: 5 credits per transcript ($0.05)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ 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(
+ "/v1/raw/tiktok/video/transcript",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"url": url}, tiktok_get_video_transcript_params.TiktokGetVideoTranscriptParams
+ ),
+ ),
+ cast_to=TiktokGetVideoTranscriptResponse,
+ )
+
+ async def list_profile_videos(
+ self,
+ username: str,
+ *,
+ cursor: str | Omit = omit,
+ region: str | Omit = omit,
+ sort_by: Literal["latest", "popular"] | 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,
+ ) -> TiktokListProfileVideosResponse:
+ """Fetch one cursor-paginated page of normalized TikTok videos.
+
+ Signed media URLs
+ are temporary and should be downloaded promptly.
+
+ **Pricing**: 0.5 credits per video page scraped ($0.005)
+
+ Args:
+ username: TikTok username, with or without a leading @
+
+ cursor: Opaque cursor from the previous response
+
+ region: Two-letter region code
+
+ sort_by: Video ordering
+
+ 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 username:
+ raise ValueError(f"Expected a non-empty value for `username` but received {username!r}")
+ return await self._get(
+ path_template("/v1/raw/tiktok/profile/{username}/videos", username=username),
+ 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,
+ "region": region,
+ "sort_by": sort_by,
+ },
+ tiktok_list_profile_videos_params.TiktokListProfileVideosParams,
+ ),
+ ),
+ cast_to=TiktokListProfileVideosResponse,
+ )
+
+ async def list_video_comments(
+ self,
+ *,
+ url: str,
+ cursor: 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,
+ ) -> TiktokListVideoCommentsResponse:
+ """
+ Fetch one cursor-paginated page of normalized comments for a TikTok video URL.
+
+ **Pricing**: 0.5 credits per comment page scraped ($0.005)
+
+ Args:
+ url: HTTPS TikTok video or share URL
+
+ cursor: Opaque cursor from the previous response
+
+ 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(
+ "/v1/raw/tiktok/video/comments",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "url": url,
+ "cursor": cursor,
+ },
+ tiktok_list_video_comments_params.TiktokListVideoCommentsParams,
+ ),
+ ),
+ cast_to=TiktokListVideoCommentsResponse,
+ )
+
+
+class TiktokResourceWithRawResponse:
+ def __init__(self, tiktok: TiktokResource) -> None:
+ self._tiktok = tiktok
+
+ self.get_profile = to_raw_response_wrapper(
+ tiktok.get_profile,
+ )
+ self.get_video = to_raw_response_wrapper(
+ tiktok.get_video,
+ )
+ self.get_video_transcript = to_raw_response_wrapper(
+ tiktok.get_video_transcript,
+ )
+ self.list_profile_videos = to_raw_response_wrapper(
+ tiktok.list_profile_videos,
+ )
+ self.list_video_comments = to_raw_response_wrapper(
+ tiktok.list_video_comments,
+ )
+
+
+class AsyncTiktokResourceWithRawResponse:
+ def __init__(self, tiktok: AsyncTiktokResource) -> None:
+ self._tiktok = tiktok
+
+ self.get_profile = async_to_raw_response_wrapper(
+ tiktok.get_profile,
+ )
+ self.get_video = async_to_raw_response_wrapper(
+ tiktok.get_video,
+ )
+ self.get_video_transcript = async_to_raw_response_wrapper(
+ tiktok.get_video_transcript,
+ )
+ self.list_profile_videos = async_to_raw_response_wrapper(
+ tiktok.list_profile_videos,
+ )
+ self.list_video_comments = async_to_raw_response_wrapper(
+ tiktok.list_video_comments,
+ )
+
+
+class TiktokResourceWithStreamingResponse:
+ def __init__(self, tiktok: TiktokResource) -> None:
+ self._tiktok = tiktok
+
+ self.get_profile = to_streamed_response_wrapper(
+ tiktok.get_profile,
+ )
+ self.get_video = to_streamed_response_wrapper(
+ tiktok.get_video,
+ )
+ self.get_video_transcript = to_streamed_response_wrapper(
+ tiktok.get_video_transcript,
+ )
+ self.list_profile_videos = to_streamed_response_wrapper(
+ tiktok.list_profile_videos,
+ )
+ self.list_video_comments = to_streamed_response_wrapper(
+ tiktok.list_video_comments,
+ )
+
+
+class AsyncTiktokResourceWithStreamingResponse:
+ def __init__(self, tiktok: AsyncTiktokResource) -> None:
+ self._tiktok = tiktok
+
+ self.get_profile = async_to_streamed_response_wrapper(
+ tiktok.get_profile,
+ )
+ self.get_video = async_to_streamed_response_wrapper(
+ tiktok.get_video,
+ )
+ self.get_video_transcript = async_to_streamed_response_wrapper(
+ tiktok.get_video_transcript,
+ )
+ self.list_profile_videos = async_to_streamed_response_wrapper(
+ tiktok.list_profile_videos,
+ )
+ self.list_video_comments = async_to_streamed_response_wrapper(
+ tiktok.list_video_comments,
+ )
diff --git a/src/influship/resources/raw/youtube.py b/src/influship/resources/raw/youtube.py
index e98c1fb..ca13d2b 100644
--- a/src/influship/resources/raw/youtube.py
+++ b/src/influship/resources/raw/youtube.py
@@ -18,12 +18,15 @@
)
from ...types.raw import (
youtube_search_params,
+ youtube_typeahead_params,
youtube_get_channel_params,
youtube_get_transcript_params,
youtube_get_channel_transcripts_params,
)
from ..._base_client import make_request_options
from ...types.raw.youtube_search_response import YoutubeSearchResponse
+from ...types.raw.youtube_get_video_response import YoutubeGetVideoResponse
+from ...types.raw.youtube_typeahead_response import YoutubeTypeaheadResponse
from ...types.raw.youtube_get_channel_response import YoutubeGetChannelResponse
from ...types.raw.youtube_get_transcript_response import YoutubeGetTranscriptResponse
from ...types.raw.youtube_get_channel_transcripts_response import YoutubeGetChannelTranscriptsResponse
@@ -76,7 +79,8 @@ def get_channel(
**Pricing**: 0.5 credits per channel scraped ($0.005)
Args:
- handle: YouTube channel handle
+ handle: YouTube channel handle or channel ID. Accepts a bare handle, an @handle, a
+ UC-prefixed channel ID, or the corresponding full channel URL.
include_videos: Include recent videos in response
@@ -140,7 +144,8 @@ def get_channel_transcripts(
**Pricing**: 0.5 credits per transcript fetched ($0.005)
Args:
- handle: YouTube channel handle
+ handle: YouTube channel handle or channel ID. Accepts a bare handle, an @handle, a
+ UC-prefixed channel ID, or the corresponding full channel URL.
include_segments: Include timestamped transcript segments in response
@@ -232,13 +237,56 @@ def get_transcript(
cast_to=YoutubeGetTranscriptResponse,
)
+ def get_video(
+ self,
+ video_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,
+ ) -> YoutubeGetVideoResponse:
+ """
+ Fetch fresh video metadata including description, exact publish timestamp when
+ available, views, likes, comments, tags, categories, and channel identity.
+
+ **Pricing**: 0.5 credits per video fetched ($0.005)
+
+ Args:
+ video_id: YouTube video ID
+
+ 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 video_id:
+ raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
+ return self._get(
+ path_template("/v1/raw/youtube/video/{video_id}", video_id=video_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=YoutubeGetVideoResponse,
+ )
+
def search(
self,
*,
q: str,
+ content_type: Literal["all", "videos"] | Omit = omit,
country_code: str | Omit = omit,
+ cursor: str | Omit = omit,
+ duration: Literal["any", "short", "medium", "long"] | Omit = omit,
language_code: str | Omit = omit,
limit: int | Omit = omit,
+ sort_by: Literal["relevance", "popular"] | Omit = omit,
+ upload_date: Literal["any", "last_hour", "today", "this_week", "this_month", "this_year"] | 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,
@@ -246,20 +294,34 @@ def search(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> YoutubeSearchResponse:
- """
- Search YouTube videos and channels.
+ """Search YouTube videos and channels.
+
+ Each request fetches one page.
- **Pricing**: 0.5 credits per result returned ($0.005)
+ Pass `next_cursor` back as `cursor` to fetch and
+ bill the next page.
+
+ **Pricing**: 0.5 credits per fetched page ($0.005)
Args:
q: Search query
+ content_type: Return all result types or only videos
+
country_code: Country code for localized results (ISO 3166-1 alpha-2)
+ cursor: Opaque cursor from next_cursor to fetch the next result page
+
+ duration: Filter videos by YouTube duration band
+
language_code: Language code for results
limit: Maximum number of results to return
+ sort_by: Order results by relevance or view popularity
+
+ upload_date: Only return results uploaded within the selected window
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -278,9 +340,14 @@ def search(
query=maybe_transform(
{
"q": q,
+ "content_type": content_type,
"country_code": country_code,
+ "cursor": cursor,
+ "duration": duration,
"language_code": language_code,
"limit": limit,
+ "sort_by": sort_by,
+ "upload_date": upload_date,
},
youtube_search_params.YoutubeSearchParams,
),
@@ -288,6 +355,54 @@ def search(
cast_to=YoutubeSearchResponse,
)
+ def typeahead(
+ self,
+ *,
+ q: str,
+ country_code: str | Omit = omit,
+ language_code: 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,
+ ) -> YoutubeTypeaheadResponse:
+ """
+ Get localized YouTube search suggestions for a partial query.
+
+ **Pricing**: 0.5 credits per request ($0.005)
+
+ Args:
+ q: Partial search query
+
+ 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(
+ "/v1/raw/youtube/typeahead",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "q": q,
+ "country_code": country_code,
+ "language_code": language_code,
+ },
+ youtube_typeahead_params.YoutubeTypeaheadParams,
+ ),
+ ),
+ cast_to=YoutubeTypeaheadResponse,
+ )
+
class AsyncYoutubeResource(AsyncAPIResource):
"""Fetch fresh data directly from social platforms in real-time.
@@ -334,7 +449,8 @@ async def get_channel(
**Pricing**: 0.5 credits per channel scraped ($0.005)
Args:
- handle: YouTube channel handle
+ handle: YouTube channel handle or channel ID. Accepts a bare handle, an @handle, a
+ UC-prefixed channel ID, or the corresponding full channel URL.
include_videos: Include recent videos in response
@@ -398,7 +514,8 @@ async def get_channel_transcripts(
**Pricing**: 0.5 credits per transcript fetched ($0.005)
Args:
- handle: YouTube channel handle
+ handle: YouTube channel handle or channel ID. Accepts a bare handle, an @handle, a
+ UC-prefixed channel ID, or the corresponding full channel URL.
include_segments: Include timestamped transcript segments in response
@@ -492,13 +609,56 @@ async def get_transcript(
cast_to=YoutubeGetTranscriptResponse,
)
+ async def get_video(
+ self,
+ video_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,
+ ) -> YoutubeGetVideoResponse:
+ """
+ Fetch fresh video metadata including description, exact publish timestamp when
+ available, views, likes, comments, tags, categories, and channel identity.
+
+ **Pricing**: 0.5 credits per video fetched ($0.005)
+
+ Args:
+ video_id: YouTube video ID
+
+ 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 video_id:
+ raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}")
+ return await self._get(
+ path_template("/v1/raw/youtube/video/{video_id}", video_id=video_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=YoutubeGetVideoResponse,
+ )
+
async def search(
self,
*,
q: str,
+ content_type: Literal["all", "videos"] | Omit = omit,
country_code: str | Omit = omit,
+ cursor: str | Omit = omit,
+ duration: Literal["any", "short", "medium", "long"] | Omit = omit,
language_code: str | Omit = omit,
limit: int | Omit = omit,
+ sort_by: Literal["relevance", "popular"] | Omit = omit,
+ upload_date: Literal["any", "last_hour", "today", "this_week", "this_month", "this_year"] | 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,
@@ -506,20 +666,34 @@ async def search(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> YoutubeSearchResponse:
- """
- Search YouTube videos and channels.
+ """Search YouTube videos and channels.
- **Pricing**: 0.5 credits per result returned ($0.005)
+ Each request fetches one page.
+
+ Pass `next_cursor` back as `cursor` to fetch and
+ bill the next page.
+
+ **Pricing**: 0.5 credits per fetched page ($0.005)
Args:
q: Search query
+ content_type: Return all result types or only videos
+
country_code: Country code for localized results (ISO 3166-1 alpha-2)
+ cursor: Opaque cursor from next_cursor to fetch the next result page
+
+ duration: Filter videos by YouTube duration band
+
language_code: Language code for results
limit: Maximum number of results to return
+ sort_by: Order results by relevance or view popularity
+
+ upload_date: Only return results uploaded within the selected window
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -538,9 +712,14 @@ async def search(
query=await async_maybe_transform(
{
"q": q,
+ "content_type": content_type,
"country_code": country_code,
+ "cursor": cursor,
+ "duration": duration,
"language_code": language_code,
"limit": limit,
+ "sort_by": sort_by,
+ "upload_date": upload_date,
},
youtube_search_params.YoutubeSearchParams,
),
@@ -548,6 +727,54 @@ async def search(
cast_to=YoutubeSearchResponse,
)
+ async def typeahead(
+ self,
+ *,
+ q: str,
+ country_code: str | Omit = omit,
+ language_code: 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,
+ ) -> YoutubeTypeaheadResponse:
+ """
+ Get localized YouTube search suggestions for a partial query.
+
+ **Pricing**: 0.5 credits per request ($0.005)
+
+ Args:
+ q: Partial search query
+
+ 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(
+ "/v1/raw/youtube/typeahead",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "q": q,
+ "country_code": country_code,
+ "language_code": language_code,
+ },
+ youtube_typeahead_params.YoutubeTypeaheadParams,
+ ),
+ ),
+ cast_to=YoutubeTypeaheadResponse,
+ )
+
class YoutubeResourceWithRawResponse:
def __init__(self, youtube: YoutubeResource) -> None:
@@ -562,9 +789,15 @@ def __init__(self, youtube: YoutubeResource) -> None:
self.get_transcript = to_raw_response_wrapper(
youtube.get_transcript,
)
+ self.get_video = to_raw_response_wrapper(
+ youtube.get_video,
+ )
self.search = to_raw_response_wrapper(
youtube.search,
)
+ self.typeahead = to_raw_response_wrapper(
+ youtube.typeahead,
+ )
class AsyncYoutubeResourceWithRawResponse:
@@ -580,9 +813,15 @@ def __init__(self, youtube: AsyncYoutubeResource) -> None:
self.get_transcript = async_to_raw_response_wrapper(
youtube.get_transcript,
)
+ self.get_video = async_to_raw_response_wrapper(
+ youtube.get_video,
+ )
self.search = async_to_raw_response_wrapper(
youtube.search,
)
+ self.typeahead = async_to_raw_response_wrapper(
+ youtube.typeahead,
+ )
class YoutubeResourceWithStreamingResponse:
@@ -598,9 +837,15 @@ def __init__(self, youtube: YoutubeResource) -> None:
self.get_transcript = to_streamed_response_wrapper(
youtube.get_transcript,
)
+ self.get_video = to_streamed_response_wrapper(
+ youtube.get_video,
+ )
self.search = to_streamed_response_wrapper(
youtube.search,
)
+ self.typeahead = to_streamed_response_wrapper(
+ youtube.typeahead,
+ )
class AsyncYoutubeResourceWithStreamingResponse:
@@ -616,6 +861,12 @@ def __init__(self, youtube: AsyncYoutubeResource) -> None:
self.get_transcript = async_to_streamed_response_wrapper(
youtube.get_transcript,
)
+ self.get_video = async_to_streamed_response_wrapper(
+ youtube.get_video,
+ )
self.search = async_to_streamed_response_wrapper(
youtube.search,
)
+ self.typeahead = async_to_streamed_response_wrapper(
+ youtube.typeahead,
+ )
diff --git a/src/influship/types/__init__.py b/src/influship/types/__init__.py
index 88a1500..de3a20f 100644
--- a/src/influship/types/__init__.py
+++ b/src/influship/types/__init__.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from .shared import CreatorBasic as CreatorBasic, ProfileSummary as ProfileSummary
+from .shared import CreatorBasic as CreatorBasic, ProfileSummary as ProfileSummary, RawScraperError as RawScraperError
from .match_info import MatchInfo as MatchInfo
from .profile_growth import ProfileGrowth as ProfileGrowth
from .profile_metrics import ProfileMetrics as ProfileMetrics
@@ -25,4 +25,6 @@
from .creator_retrieve_response import CreatorRetrieveResponse as CreatorRetrieveResponse
from .creator_lookalike_response import CreatorLookalikeResponse as CreatorLookalikeResponse
from .creator_autocomplete_params import CreatorAutocompleteParams as CreatorAutocompleteParams
+from .creator_email_lookup_params import CreatorEmailLookupParams as CreatorEmailLookupParams
from .creator_autocomplete_response import CreatorAutocompleteResponse as CreatorAutocompleteResponse
+from .creator_email_lookup_response import CreatorEmailLookupResponse as CreatorEmailLookupResponse
diff --git a/src/influship/types/creator_email_lookup_params.py b/src/influship/types/creator_email_lookup_params.py
new file mode 100644
index 0000000..2e77b44
--- /dev/null
+++ b/src/influship/types/creator_email_lookup_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 import Union, Iterable
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+__all__ = ["CreatorEmailLookupParams", "Creator", "CreatorCreatorEmailLookupByID", "CreatorCreatorEmailLookupByHandle"]
+
+
+class CreatorEmailLookupParams(TypedDict, total=False):
+ creators: Required[Iterable[Creator]]
+ """Creator lookups to resolve. Response rows preserve this input order."""
+
+
+class CreatorCreatorEmailLookupByID(TypedDict, total=False):
+ """Creator email lookup input by creator ID"""
+
+ creator_id: Required[str]
+ """Creator profile ID to look up directly"""
+
+
+class CreatorCreatorEmailLookupByHandle(TypedDict, total=False):
+ """Creator email lookup input by social handle"""
+
+ platform: Required[Literal["instagram"]]
+ """Social platform for handle-based lookup"""
+
+ username: Required[str]
+ """Social username for handle-based lookup"""
+
+
+Creator: TypeAlias = Union[CreatorCreatorEmailLookupByID, CreatorCreatorEmailLookupByHandle]
diff --git a/src/influship/types/creator_email_lookup_response.py b/src/influship/types/creator_email_lookup_response.py
new file mode 100644
index 0000000..823f219
--- /dev/null
+++ b/src/influship/types/creator_email_lookup_response.py
@@ -0,0 +1,104 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, TypeAlias
+
+from .._models import BaseModel
+
+__all__ = [
+ "CreatorEmailLookupResponse",
+ "Data",
+ "DataBilling",
+ "DataResult",
+ "DataResultEmail",
+ "DataResultInput",
+ "DataResultInputCreatorEmailLookupByIDOutput",
+ "DataResultInputCreatorEmailLookupByHandleOutput",
+]
+
+
+class DataBilling(BaseModel):
+ """Creator email lookup billing summary"""
+
+ billable_results: int
+ """Unique resolved creators with at least one returned email"""
+
+ credits_charged: float
+ """Preview of credits charged for this lookup"""
+
+
+class DataResultEmail(BaseModel):
+ """API-visible creator email"""
+
+ confidence: Optional[float] = None
+ """Nullable confidence score for the email"""
+
+ email: str
+ """Email address as stored, preserving original casing"""
+
+ first_seen_at: datetime
+ """When Influship first observed this email"""
+
+ is_primary: bool
+ """Whether this is the primary email for the creator"""
+
+ last_seen_at: datetime
+ """When Influship most recently observed this email"""
+
+ status: Literal["unvalidated", "valid", "risky", "creator_verified"]
+ """API-visible email validation status"""
+
+ validated_at: Optional[datetime] = None
+ """When the email was last validated, if known"""
+
+
+class DataResultInputCreatorEmailLookupByIDOutput(BaseModel):
+ """Creator email lookup input by creator ID"""
+
+ creator_id: str
+ """Creator profile ID to look up directly"""
+
+
+class DataResultInputCreatorEmailLookupByHandleOutput(BaseModel):
+ """Creator email lookup input by social handle"""
+
+ platform: Literal["instagram"]
+ """Social platform for handle-based lookup"""
+
+ username: str
+ """Social username for handle-based lookup"""
+
+
+DataResultInput: TypeAlias = Union[
+ DataResultInputCreatorEmailLookupByIDOutput, DataResultInputCreatorEmailLookupByHandleOutput
+]
+
+
+class DataResult(BaseModel):
+ """Creator email lookup result"""
+
+ creator_id: Optional[str] = None
+ """Resolved creator ID, or null when the input could not be resolved"""
+
+ emails: List[DataResultEmail]
+ """API-visible emails for the resolved creator. Empty results are not billable."""
+
+ input: DataResultInput
+ """Creator email lookup input by creator ID or social handle"""
+
+ resolved: bool
+ """Whether the lookup resolved to a creator profile"""
+
+
+class Data(BaseModel):
+ billing: DataBilling
+ """Creator email lookup billing summary"""
+
+ results: List[DataResult]
+
+
+class CreatorEmailLookupResponse(BaseModel):
+ """Creator email lookup response"""
+
+ data: Data
diff --git a/src/influship/types/creator_match_response.py b/src/influship/types/creator_match_response.py
index 0b13ef4..2418e8d 100644
--- a/src/influship/types/creator_match_response.py
+++ b/src/influship/types/creator_match_response.py
@@ -26,9 +26,25 @@ class DataInput(BaseModel):
class DataMatchReason(BaseModel):
+ provenance: Literal["post_evidence", "profile_fact", "inferred"]
+ """How grounded a match reason is, strongest first.
+
+ `post_evidence`: backed by a specific post you can open (see `source_post_id` /
+ `evidence_quote`). `profile_fact`: backed by a stored profile fact without a
+ clickable source post — weaker than post-backed. `inferred`: model reasoning
+ over the profile with no direct post evidence.
+ """
+
text: str
"""Human-readable reason for the match"""
+ evidence_quote: Optional[str] = None
+ """Verbatim sentence from the source post that best supports this reason.
+
+ Present only for `post_evidence` reasons where a genuinely supporting sentence
+ exists.
+ """
+
fact_id: Optional[str] = None
"""ID of the supporting fact, if applicable"""
diff --git a/src/influship/types/creator_retrieve_response.py b/src/influship/types/creator_retrieve_response.py
index 7c1f080..e7032ce 100644
--- a/src/influship/types/creator_retrieve_response.py
+++ b/src/influship/types/creator_retrieve_response.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
+from datetime import datetime
from .._models import BaseModel
from .shared.profile_summary import ProfileSummary
@@ -17,6 +18,12 @@ class Data(BaseModel):
ai_summary: Optional[str] = None
"""AI-generated summary of the creator"""
+ analysis_fact_count: Optional[int] = None
+ """Number of stored creator facts available to the last synthesis run"""
+
+ analysis_updated_at: Optional[datetime] = None
+ """When the synthesized creator analysis was last generated"""
+
audience_demographics: Optional[str] = None
"""Synthesized audience demographic summary"""
diff --git a/src/influship/types/match_info.py b/src/influship/types/match_info.py
index 58f1d8e..c6053a2 100644
--- a/src/influship/types/match_info.py
+++ b/src/influship/types/match_info.py
@@ -1,17 +1,76 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List
+from typing import List, Optional
+from typing_extensions import Literal
from .._models import BaseModel
-__all__ = ["MatchInfo"]
+__all__ = ["MatchInfo", "Evidence"]
+
+
+class Evidence(BaseModel):
+ """A single evidence-grounded match reason."""
+
+ evidence_quote: Optional[str] = None
+ """
+ Verbatim sentence copied from the source post caption/transcript that best
+ supports this reason. Present only for `post_evidence` reasons where a genuinely
+ supporting sentence exists — omitted (null) rather than filled with unrelated
+ post text. Never model-generated.
+ """
+
+ fact_id: Optional[str] = None
+ """Stored profile fact backing this reason, or null."""
+
+ provenance: Literal["post_evidence", "profile_fact", "inferred"]
+ """How grounded a match reason is, strongest first.
+
+ `post_evidence`: backed by a specific post you can open (see `source_post_id` /
+ `evidence_quote`). `profile_fact`: backed by a stored profile fact without a
+ clickable source post — weaker than post-backed. `inferred`: model reasoning
+ over the profile with no direct post evidence.
+ """
+
+ source_post_id: Optional[str] = None
+ """
+ Post that evidences this reason (use with GET /v1/posts/{id}), or null for
+ non-post-backed reasons.
+ """
+
+ text: str
+ """Human-readable reason this creator matched."""
class MatchInfo(BaseModel):
"""Search match information"""
+ confidence: float
+ """Rerank relevance as a 0-1 confidence. Mirrors `score`."""
+
+ evidence: List[Evidence]
+ """
+ Evidence-grounded version of `reasons`, in the same order: each reason has a
+ provenance label and, where it rests on a post, the backing `source_post_id` and
+ a verbatim `evidence_quote`.
+ """
+
+ low_confidence: bool
+ """True when `confidence` is at/below the low-confidence threshold.
+
+ A non-breaking marker for the weak tail so you can separate "weaker matches"
+ instead of treating every result as a strong match.
+ """
+
reasons: List[str]
- """Human-readable match reasons"""
+ """Human-readable match reasons (plain text)."""
score: float
"""Match relevance score (0-1)"""
+
+ ranking_source: Optional[Literal["reranked", "retrieval_fallback"]] = None
+ """
+ Whether this individual result was covered by AI reranking or conservatively
+ reconciled from retrieval. Treat absence as reranked for responses from older
+ servers during rolling deployment. Do not present `score` as an AI fit score for
+ retrieval_fallback results.
+ """
diff --git a/src/influship/types/post_list_params.py b/src/influship/types/post_list_params.py
index ffbe981..b23df4e 100644
--- a/src/influship/types/post_list_params.py
+++ b/src/influship/types/post_list_params.py
@@ -12,7 +12,11 @@ class PostListParams(TypedDict, total=False):
"""Creator ID (use this OR platform+username)"""
cursor: str
- """Pagination cursor for next page"""
+ """Opaque keyset cursor from next_cursor.
+
+ A cursor is bound to the sort order that produced it; using it with another sort
+ returns 400.
+ """
limit: int
"""Maximum posts to return"""
diff --git a/src/influship/types/post_list_response.py b/src/influship/types/post_list_response.py
index 48cd5e8..130bae5 100644
--- a/src/influship/types/post_list_response.py
+++ b/src/influship/types/post_list_response.py
@@ -6,7 +6,7 @@
from .._models import BaseModel
-__all__ = ["PostListResponse", "Location", "Media", "Metrics"]
+__all__ = ["PostListResponse", "Location", "Media", "MediaCarouselItem", "Metrics"]
class Location(BaseModel):
@@ -16,20 +16,40 @@ class Location(BaseModel):
"""Location name"""
+class MediaCarouselItem(BaseModel):
+ index: int
+ """Zero-based position in the carousel."""
+
+ is_video: bool
+ """True if this item is a video."""
+
+ thumbnail_url: Optional[str] = None
+ """Thumbnail URL for this item. Cover frame for videos."""
+
+
class Media(BaseModel):
"""Post media information"""
+ carousel_items: Optional[List[MediaCarouselItem]] = None
+ """Per-item structure for carousel posts.
+
+ Null for non-carousel posts. Per-item video_url is intentionally omitted (would
+ be stale). For fresh video URLs, call GET /v1/raw/instagram/post/{shortcode}.
+ """
+
duration_seconds: Optional[float] = None
- """Video duration in seconds"""
+ """Video duration in seconds. Null for non-video posts."""
thumbnail_url: Optional[str] = None
- """Thumbnail URL"""
+ """Thumbnail URL. For videos, this is the cover frame."""
url: Optional[str] = None
- """Media URL"""
+ """Cover/primary image URL for image and carousel posts.
- video_url: Optional[str] = None
- """Video URL (for video content)"""
+ Null for video posts — call GET /v1/raw/instagram/post/{shortcode} for a fresh,
+ downloadable video URL. Note: returned image URLs are Instagram CDN URLs and may
+ expire; a future change will migrate to persistent R2-hosted URLs.
+ """
class Metrics(BaseModel):
diff --git a/src/influship/types/raw/__init__.py b/src/influship/types/raw/__init__.py
index 2afb1d6..7db8c5e 100644
--- a/src/influship/types/raw/__init__.py
+++ b/src/influship/types/raw/__init__.py
@@ -2,13 +2,21 @@
from __future__ import annotations
-from .raw_scraper_error import RawScraperError as RawScraperError
+from .video import Video as Video
+from .profile import Profile as Profile
+from .transcript import Transcript as Transcript
from .transcript_segment import TranscriptSegment as TranscriptSegment
from .youtube_search_params import YoutubeSearchParams as YoutubeSearchParams
+from .tiktok_get_video_params import TiktokGetVideoParams as TiktokGetVideoParams
from .youtube_search_response import YoutubeSearchResponse as YoutubeSearchResponse
+from .youtube_typeahead_params import YoutubeTypeaheadParams as YoutubeTypeaheadParams
+from .tiktok_get_video_response import TiktokGetVideoResponse as TiktokGetVideoResponse
from .instagram_get_posts_params import InstagramGetPostsParams as InstagramGetPostsParams
from .youtube_get_channel_params import YoutubeGetChannelParams as YoutubeGetChannelParams
+from .youtube_get_video_response import YoutubeGetVideoResponse as YoutubeGetVideoResponse
+from .youtube_typeahead_response import YoutubeTypeaheadResponse as YoutubeTypeaheadResponse
from .instagram_get_post_response import InstagramGetPostResponse as InstagramGetPostResponse
+from .tiktok_get_profile_response import TiktokGetProfileResponse as TiktokGetProfileResponse
from .instagram_get_posts_response import InstagramGetPostsResponse as InstagramGetPostsResponse
from .instagram_get_profile_params import InstagramGetProfileParams as InstagramGetProfileParams
from .youtube_get_channel_response import YoutubeGetChannelResponse as YoutubeGetChannelResponse
@@ -20,7 +28,13 @@
from .youtube_get_transcript_response import YoutubeGetTranscriptResponse as YoutubeGetTranscriptResponse
from .instagram_get_transcripts_params import InstagramGetTranscriptsParams as InstagramGetTranscriptsParams
from .instagram_get_transcript_response import InstagramGetTranscriptResponse as InstagramGetTranscriptResponse
+from .tiktok_list_profile_videos_params import TiktokListProfileVideosParams as TiktokListProfileVideosParams
+from .tiktok_list_video_comments_params import TiktokListVideoCommentsParams as TiktokListVideoCommentsParams
from .instagram_get_transcripts_response import InstagramGetTranscriptsResponse as InstagramGetTranscriptsResponse
+from .tiktok_get_video_transcript_params import TiktokGetVideoTranscriptParams as TiktokGetVideoTranscriptParams
+from .tiktok_list_profile_videos_response import TiktokListProfileVideosResponse as TiktokListProfileVideosResponse
+from .tiktok_list_video_comments_response import TiktokListVideoCommentsResponse as TiktokListVideoCommentsResponse
+from .tiktok_get_video_transcript_response import TiktokGetVideoTranscriptResponse as TiktokGetVideoTranscriptResponse
from .youtube_get_channel_transcripts_params import (
YoutubeGetChannelTranscriptsParams as YoutubeGetChannelTranscriptsParams,
)
diff --git a/src/influship/types/raw/instagram_get_posts_response.py b/src/influship/types/raw/instagram_get_posts_response.py
index 18e9d0d..31dc28c 100644
--- a/src/influship/types/raw/instagram_get_posts_response.py
+++ b/src/influship/types/raw/instagram_get_posts_response.py
@@ -5,7 +5,7 @@
from typing_extensions import Literal, TypeAlias
from ..._models import BaseModel
-from .raw_scraper_error import RawScraperError
+from ..shared.raw_scraper_error import RawScraperError
from .instagram_single_post_response import InstagramSinglePostResponse
__all__ = [
diff --git a/src/influship/types/raw/instagram_get_transcripts_response.py b/src/influship/types/raw/instagram_get_transcripts_response.py
index d82bb35..bfc7e9d 100644
--- a/src/influship/types/raw/instagram_get_transcripts_response.py
+++ b/src/influship/types/raw/instagram_get_transcripts_response.py
@@ -5,7 +5,7 @@
from typing_extensions import Literal, TypeAlias
from ..._models import BaseModel
-from .raw_scraper_error import RawScraperError
+from ..shared.raw_scraper_error import RawScraperError
from .instagram_transcript_response import InstagramTranscriptResponse
__all__ = [
diff --git a/src/influship/types/raw/instagram_transcript_response.py b/src/influship/types/raw/instagram_transcript_response.py
index 84646ac..1394fe7 100644
--- a/src/influship/types/raw/instagram_transcript_response.py
+++ b/src/influship/types/raw/instagram_transcript_response.py
@@ -154,8 +154,6 @@ class InstagramTranscriptResponse(BaseModel):
language: str
- post: Post
-
scraped_at: datetime
shortcode: str
@@ -165,3 +163,5 @@ class InstagramTranscriptResponse(BaseModel):
word_count: float
duration_seconds: Optional[float] = None
+
+ post: Optional[Post] = None
diff --git a/src/influship/types/raw/profile.py b/src/influship/types/raw/profile.py
new file mode 100644
index 0000000..4e95c1d
--- /dev/null
+++ b/src/influship/types/raw/profile.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["Profile"]
+
+
+class Profile(BaseModel):
+ avatar_url: Optional[str] = None
+
+ biography: str
+
+ display_name: str
+
+ external_url: Optional[str] = None
+
+ follower_count: int
+
+ following_count: int
+
+ is_business: bool
+
+ is_private: bool
+
+ is_verified: bool
+
+ like_count: int
+
+ profile_url: str
+
+ scraped_at: datetime
+
+ user_id: str
+
+ username: str
+
+ video_count: int
diff --git a/src/influship/types/raw/tiktok_get_profile_response.py b/src/influship/types/raw/tiktok_get_profile_response.py
new file mode 100644
index 0000000..138f904
--- /dev/null
+++ b/src/influship/types/raw/tiktok_get_profile_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .profile import Profile
+from ..._models import BaseModel
+
+__all__ = ["TiktokGetProfileResponse"]
+
+
+class TiktokGetProfileResponse(BaseModel):
+ data: Profile
diff --git a/src/influship/types/raw/tiktok_get_video_params.py b/src/influship/types/raw/tiktok_get_video_params.py
new file mode 100644
index 0000000..a3c4c0b
--- /dev/null
+++ b/src/influship/types/raw/tiktok_get_video_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["TiktokGetVideoParams"]
+
+
+class TiktokGetVideoParams(TypedDict, total=False):
+ url: Required[str]
+ """HTTPS TikTok video or share URL"""
+
+ region: str
+ """Two-letter region code"""
diff --git a/src/influship/types/raw/tiktok_get_video_response.py b/src/influship/types/raw/tiktok_get_video_response.py
new file mode 100644
index 0000000..353481a
--- /dev/null
+++ b/src/influship/types/raw/tiktok_get_video_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from .video import Video
+from ..._models import BaseModel
+
+__all__ = ["TiktokGetVideoResponse", "Data"]
+
+
+class Data(BaseModel):
+ scraped_at: datetime
+
+ video: Video
+
+
+class TiktokGetVideoResponse(BaseModel):
+ data: Data
diff --git a/src/influship/types/raw/tiktok_get_video_transcript_params.py b/src/influship/types/raw/tiktok_get_video_transcript_params.py
new file mode 100644
index 0000000..06fe602
--- /dev/null
+++ b/src/influship/types/raw/tiktok_get_video_transcript_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["TiktokGetVideoTranscriptParams"]
+
+
+class TiktokGetVideoTranscriptParams(TypedDict, total=False):
+ url: Required[str]
+ """HTTPS TikTok video or share URL"""
diff --git a/src/influship/types/raw/tiktok_get_video_transcript_response.py b/src/influship/types/raw/tiktok_get_video_transcript_response.py
new file mode 100644
index 0000000..14d57ec
--- /dev/null
+++ b/src/influship/types/raw/tiktok_get_video_transcript_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .transcript import Transcript
+
+__all__ = ["TiktokGetVideoTranscriptResponse"]
+
+
+class TiktokGetVideoTranscriptResponse(BaseModel):
+ data: Transcript
diff --git a/src/influship/types/raw/tiktok_list_profile_videos_params.py b/src/influship/types/raw/tiktok_list_profile_videos_params.py
new file mode 100644
index 0000000..507f8f7
--- /dev/null
+++ b/src/influship/types/raw/tiktok_list_profile_videos_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["TiktokListProfileVideosParams"]
+
+
+class TiktokListProfileVideosParams(TypedDict, total=False):
+ cursor: str
+ """Opaque cursor from the previous response"""
+
+ region: str
+ """Two-letter region code"""
+
+ sort_by: Literal["latest", "popular"]
+ """Video ordering"""
diff --git a/src/influship/types/raw/tiktok_list_profile_videos_response.py b/src/influship/types/raw/tiktok_list_profile_videos_response.py
new file mode 100644
index 0000000..61526a6
--- /dev/null
+++ b/src/influship/types/raw/tiktok_list_profile_videos_response.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .video import Video
+from ..._models import BaseModel
+
+__all__ = ["TiktokListProfileVideosResponse", "Data"]
+
+
+class Data(BaseModel):
+ has_more: bool
+
+ next_cursor: Optional[str] = None
+
+ scraped_at: datetime
+
+ username: str
+
+ videos: List[Video]
+
+
+class TiktokListProfileVideosResponse(BaseModel):
+ data: Data
diff --git a/src/influship/types/raw/tiktok_list_video_comments_params.py b/src/influship/types/raw/tiktok_list_video_comments_params.py
new file mode 100644
index 0000000..9f28562
--- /dev/null
+++ b/src/influship/types/raw/tiktok_list_video_comments_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["TiktokListVideoCommentsParams"]
+
+
+class TiktokListVideoCommentsParams(TypedDict, total=False):
+ url: Required[str]
+ """HTTPS TikTok video or share URL"""
+
+ cursor: str
+ """Opaque cursor from the previous response"""
diff --git a/src/influship/types/raw/tiktok_list_video_comments_response.py b/src/influship/types/raw/tiktok_list_video_comments_response.py
new file mode 100644
index 0000000..785edad
--- /dev/null
+++ b/src/influship/types/raw/tiktok_list_video_comments_response.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["TiktokListVideoCommentsResponse", "Data", "DataComment", "DataCommentAuthor"]
+
+
+class DataCommentAuthor(BaseModel):
+ avatar_url: Optional[str] = None
+
+ display_name: str
+
+ is_verified: bool
+
+ user_id: str
+
+ username: str
+
+
+class DataComment(BaseModel):
+ author: DataCommentAuthor
+
+ comment_id: str
+
+ created_at: Optional[datetime] = None
+
+ is_pinned: bool
+
+ like_count: int
+
+ reply_count: int
+
+ text: str
+
+ video_id: str
+
+
+class Data(BaseModel):
+ comments: List[DataComment]
+
+ has_more: bool
+
+ next_cursor: Optional[str] = None
+
+ scraped_at: datetime
+
+ total: Optional[int] = None
+
+ video_id: Optional[str] = None
+
+
+class TiktokListVideoCommentsResponse(BaseModel):
+ data: Data
diff --git a/src/influship/types/raw/transcript.py b/src/influship/types/raw/transcript.py
new file mode 100644
index 0000000..28ba28d
--- /dev/null
+++ b/src/influship/types/raw/transcript.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["Transcript", "Segment"]
+
+
+class Segment(BaseModel):
+ end_ms: int
+
+ start_ms: int
+
+ text: str
+
+
+class Transcript(BaseModel):
+ duration_seconds: Optional[float] = None
+
+ full_text: str
+
+ language: str
+
+ scraped_at: datetime
+
+ segments: List[Segment]
+
+ source: Literal["captions", "generated"]
+
+ transcript: str
+
+ url: str
+
+ video_id: str
+
+ word_count: int
diff --git a/src/influship/types/raw/video.py b/src/influship/types/raw/video.py
new file mode 100644
index 0000000..82433e6
--- /dev/null
+++ b/src/influship/types/raw/video.py
@@ -0,0 +1,71 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["Video", "Author", "Music"]
+
+
+class Author(BaseModel):
+ avatar_url: Optional[str] = None
+
+ display_name: str
+
+ is_verified: bool
+
+ user_id: str
+
+ username: str
+
+
+class Music(BaseModel):
+ author: Optional[str] = None
+
+ duration_seconds: Optional[float] = None
+
+ music_id: Optional[str] = None
+
+ title: Optional[str] = None
+
+
+class Video(BaseModel):
+ author: Author
+
+ comment_count: int
+
+ created_at: Optional[datetime] = None
+
+ description: str
+
+ duration_seconds: Optional[float] = None
+
+ has_watermark: Optional[bool] = None
+
+ hashtags: List[str]
+
+ images: List[str]
+
+ is_pinned: bool
+
+ like_count: int
+
+ media_type: Literal["video", "slideshow"]
+
+ music: Optional[Music] = None
+
+ save_count: int
+
+ share_count: int
+
+ thumbnail_url: Optional[str] = None
+
+ url: str
+
+ video_id: str
+
+ video_url: Optional[str] = None
+
+ view_count: int
diff --git a/src/influship/types/raw/youtube_get_video_response.py b/src/influship/types/raw/youtube_get_video_response.py
new file mode 100644
index 0000000..49cc71d
--- /dev/null
+++ b/src/influship/types/raw/youtube_get_video_response.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["YoutubeGetVideoResponse", "Data"]
+
+
+class Data(BaseModel):
+ availability: Optional[str] = None
+
+ categories: List[str]
+
+ channel_handle: Optional[str] = None
+
+ channel_id: Optional[str] = None
+
+ channel_name: Optional[str] = None
+
+ comment_count: Optional[int] = None
+
+ description: str
+
+ duration_seconds: Optional[float] = None
+
+ like_count: Optional[int] = None
+
+ live_status: Optional[str] = None
+
+ published_at: Optional[datetime] = None
+ """Exact publish timestamp when supplied by YouTube, otherwise null"""
+
+ published_date: Optional[str] = None
+ """Publish date in YYYY-MM-DD format when available"""
+
+ scraped_at: datetime
+
+ tags: List[str]
+
+ thumbnail_url: Optional[str] = None
+
+ title: str
+
+ url: str
+
+ video_id: str
+
+ view_count: Optional[int] = None
+
+
+class YoutubeGetVideoResponse(BaseModel):
+ data: Data
diff --git a/src/influship/types/raw/youtube_search_params.py b/src/influship/types/raw/youtube_search_params.py
index e3c644f..e9307cd 100644
--- a/src/influship/types/raw/youtube_search_params.py
+++ b/src/influship/types/raw/youtube_search_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Required, TypedDict
+from typing_extensions import Literal, Required, TypedDict
__all__ = ["YoutubeSearchParams"]
@@ -11,11 +11,26 @@ class YoutubeSearchParams(TypedDict, total=False):
q: Required[str]
"""Search query"""
+ content_type: Literal["all", "videos"]
+ """Return all result types or only videos"""
+
country_code: str
"""Country code for localized results (ISO 3166-1 alpha-2)"""
+ cursor: str
+ """Opaque cursor from next_cursor to fetch the next result page"""
+
+ duration: Literal["any", "short", "medium", "long"]
+ """Filter videos by YouTube duration band"""
+
language_code: str
"""Language code for results"""
limit: int
"""Maximum number of results to return"""
+
+ sort_by: Literal["relevance", "popular"]
+ """Order results by relevance or view popularity"""
+
+ upload_date: Literal["any", "last_hour", "today", "this_week", "this_month", "this_year"]
+ """Only return results uploaded within the selected window"""
diff --git a/src/influship/types/raw/youtube_search_response.py b/src/influship/types/raw/youtube_search_response.py
index 7f02b4d..06872d1 100644
--- a/src/influship/types/raw/youtube_search_response.py
+++ b/src/influship/types/raw/youtube_search_response.py
@@ -101,6 +101,9 @@ class Data(BaseModel):
estimated_results: Optional[float] = None
"""Estimated total results count"""
+ next_cursor: Optional[str] = None
+ """Opaque cursor for the next page, or null when no next page is available"""
+
query: str
"""The search query"""
diff --git a/src/influship/types/raw/youtube_typeahead_params.py b/src/influship/types/raw/youtube_typeahead_params.py
new file mode 100644
index 0000000..bc66423
--- /dev/null
+++ b/src/influship/types/raw/youtube_typeahead_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["YoutubeTypeaheadParams"]
+
+
+class YoutubeTypeaheadParams(TypedDict, total=False):
+ q: Required[str]
+ """Partial search query"""
+
+ country_code: str
+
+ language_code: str
diff --git a/src/influship/types/raw/youtube_typeahead_response.py b/src/influship/types/raw/youtube_typeahead_response.py
new file mode 100644
index 0000000..4d50aba
--- /dev/null
+++ b/src/influship/types/raw/youtube_typeahead_response.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["YoutubeTypeaheadResponse", "Data"]
+
+
+class Data(BaseModel):
+ query: str
+
+ scraped_at: datetime
+
+ suggestions: List[str]
+
+
+class YoutubeTypeaheadResponse(BaseModel):
+ data: Data
diff --git a/src/influship/types/search_create_response.py b/src/influship/types/search_create_response.py
index 723be86..deaf0c8 100644
--- a/src/influship/types/search_create_response.py
+++ b/src/influship/types/search_create_response.py
@@ -1,19 +1,34 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import List, Union, Optional
+from typing_extensions import Literal, TypeAlias
from .._models import BaseModel
from .match_info import MatchInfo
from .shared.creator_basic import CreatorBasic
from .shared.profile_summary import ProfileSummary
-__all__ = ["SearchCreateResponse", "Data"]
+__all__ = [
+ "SearchCreateResponse",
+ "Data",
+ "Quality",
+ "QualityUnionMember0",
+ "QualityUnionMember1",
+ "QualityUnionMember2",
+]
class Data(BaseModel):
creator: CreatorBasic
"""Basic creator information"""
+ location_unverified: Optional[bool] = None
+ """
+ True when the query required a country but this creator location is unverified;
+ false when the known location satisfies it; null when the query set no geography
+ requirement. Known mismatches are excluded from results.
+ """
+
match: MatchInfo
"""Search match information"""
@@ -24,6 +39,31 @@ class Data(BaseModel):
"""Abbreviated profile information"""
+class QualityUnionMember0(BaseModel):
+ mode: Literal["reranked"]
+
+ reason: None = None
+
+
+class QualityUnionMember1(BaseModel):
+ mode: Literal["partially_reranked"]
+
+ reason: Literal[
+ "content_policy", "incomplete_rerank_output", "partial_batch_failure", "provider_error", "soft_timeout"
+ ]
+
+
+class QualityUnionMember2(BaseModel):
+ mode: Literal["retrieval_fallback"]
+
+ reason: Literal[
+ "content_policy", "incomplete_rerank_output", "partial_batch_failure", "provider_error", "soft_timeout"
+ ]
+
+
+Quality: TypeAlias = Union[QualityUnionMember0, QualityUnionMember1, QualityUnionMember2]
+
+
class SearchCreateResponse(BaseModel):
data: List[Data]
@@ -38,3 +78,11 @@ class SearchCreateResponse(BaseModel):
total: int
"""Total number of results across all pages"""
+
+ quality: Optional[Quality] = None
+ """
+ Whether results were fully AI-reranked, partially reranked, or returned from
+ retrieval fallback. Treat absence as reranked for responses from older servers
+ during rolling deployment. Inspect each match.ranking_source before presenting
+ its score as an AI fit score.
+ """
diff --git a/src/influship/types/search_retrieve_response.py b/src/influship/types/search_retrieve_response.py
index b36e9c7..ac5fcd3 100644
--- a/src/influship/types/search_retrieve_response.py
+++ b/src/influship/types/search_retrieve_response.py
@@ -14,6 +14,13 @@ class SearchRetrieveResponse(BaseModel):
creator: CreatorBasic
"""Basic creator information"""
+ location_unverified: Optional[bool] = None
+ """
+ True when the query required a country but this creator location is unverified;
+ false when the known location satisfies it; null when the query set no geography
+ requirement. Known mismatches are excluded from results.
+ """
+
match: MatchInfo
"""Search match information"""
diff --git a/src/influship/types/shared/__init__.py b/src/influship/types/shared/__init__.py
index 7e07f42..4d440b3 100644
--- a/src/influship/types/shared/__init__.py
+++ b/src/influship/types/shared/__init__.py
@@ -2,3 +2,4 @@
from .creator_basic import CreatorBasic as CreatorBasic
from .profile_summary import ProfileSummary as ProfileSummary
+from .raw_scraper_error import RawScraperError as RawScraperError
diff --git a/src/influship/types/shared/profile_summary.py b/src/influship/types/shared/profile_summary.py
index 1873eed..1d301ea 100644
--- a/src/influship/types/shared/profile_summary.py
+++ b/src/influship/types/shared/profile_summary.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
+from datetime import datetime
from typing_extensions import Literal
from ..._models import BaseModel
@@ -14,6 +15,9 @@ class ProfileSummary(BaseModel):
id: str
"""Profile unique identifier"""
+ data_updated_at: Optional[datetime] = None
+ """When the stored public profile data was last refreshed"""
+
engagement_rate: Optional[float] = None
"""Engagement rate as a percentage, null if unknown (e.g. 3.5 means 3.5%)"""
diff --git a/src/influship/types/raw/raw_scraper_error.py b/src/influship/types/shared/raw_scraper_error.py
similarity index 59%
rename from src/influship/types/raw/raw_scraper_error.py
rename to src/influship/types/shared/raw_scraper_error.py
index 439b831..7cd7294 100644
--- a/src/influship/types/raw/raw_scraper_error.py
+++ b/src/influship/types/shared/raw_scraper_error.py
@@ -9,7 +9,17 @@
class RawScraperError(BaseModel):
- error: Literal["not_found", "private", "rate_limited", "blocked", "invalid_input", "timeout", "unknown"]
+ error: Literal[
+ "not_found",
+ "private",
+ "rate_limited",
+ "blocked",
+ "invalid_input",
+ "timeout",
+ "upstream_unavailable",
+ "upstream_contract_broken",
+ "unknown",
+ ]
message: str
diff --git a/tests/api_resources/raw/test_tiktok.py b/tests/api_resources/raw/test_tiktok.py
new file mode 100644
index 0000000..5682095
--- /dev/null
+++ b/tests/api_resources/raw/test_tiktok.py
@@ -0,0 +1,460 @@
+# 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 influship import Influship, AsyncInfluship
+from tests.utils import assert_matches_type
+from influship.types.raw import (
+ TiktokGetVideoResponse,
+ TiktokGetProfileResponse,
+ TiktokListProfileVideosResponse,
+ TiktokListVideoCommentsResponse,
+ TiktokGetVideoTranscriptResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestTiktok:
+ 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_get_profile(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.get_profile(
+ "creator",
+ )
+ assert_matches_type(TiktokGetProfileResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get_profile(self, client: Influship) -> None:
+ response = client.raw.tiktok.with_raw_response.get_profile(
+ "creator",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetProfileResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get_profile(self, client: Influship) -> None:
+ with client.raw.tiktok.with_streaming_response.get_profile(
+ "creator",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetProfileResponse, tiktok, 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_profile(self, client: Influship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `username` but received ''"):
+ client.raw.tiktok.with_raw_response.get_profile(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_video(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_video_with_all_params(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ region="US",
+ )
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get_video(self, client: Influship) -> None:
+ response = client.raw.tiktok.with_raw_response.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get_video(self, client: Influship) -> None:
+ with client.raw.tiktok.with_streaming_response.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_video_transcript(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get_video_transcript(self, client: Influship) -> None:
+ response = client.raw.tiktok.with_raw_response.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get_video_transcript(self, client: Influship) -> None:
+ with client.raw.tiktok.with_streaming_response.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = response.parse()
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_profile_videos(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.list_profile_videos(
+ username="creator",
+ )
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_profile_videos_with_all_params(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.list_profile_videos(
+ username="creator",
+ cursor="x",
+ region="US",
+ sort_by="latest",
+ )
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_profile_videos(self, client: Influship) -> None:
+ response = client.raw.tiktok.with_raw_response.list_profile_videos(
+ username="creator",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = response.parse()
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_profile_videos(self, client: Influship) -> None:
+ with client.raw.tiktok.with_streaming_response.list_profile_videos(
+ username="creator",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = response.parse()
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list_profile_videos(self, client: Influship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `username` but received ''"):
+ client.raw.tiktok.with_raw_response.list_profile_videos(
+ username="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_video_comments(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_video_comments_with_all_params(self, client: Influship) -> None:
+ tiktok = client.raw.tiktok.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ cursor="x",
+ )
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_video_comments(self, client: Influship) -> None:
+ response = client.raw.tiktok.with_raw_response.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = response.parse()
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_video_comments(self, client: Influship) -> None:
+ with client.raw.tiktok.with_streaming_response.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = response.parse()
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncTiktok:
+ 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_get_profile(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.get_profile(
+ "creator",
+ )
+ assert_matches_type(TiktokGetProfileResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get_profile(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.tiktok.with_raw_response.get_profile(
+ "creator",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetProfileResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get_profile(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.tiktok.with_streaming_response.get_profile(
+ "creator",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetProfileResponse, tiktok, 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_profile(self, async_client: AsyncInfluship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `username` but received ''"):
+ await async_client.raw.tiktok.with_raw_response.get_profile(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_video(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_video_with_all_params(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ region="US",
+ )
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get_video(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.tiktok.with_raw_response.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get_video(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.tiktok.with_streaming_response.get_video(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetVideoResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_video_transcript(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get_video_transcript(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.tiktok.with_raw_response.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get_video_transcript(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.tiktok.with_streaming_response.get_video_transcript(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = await response.parse()
+ assert_matches_type(TiktokGetVideoTranscriptResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_profile_videos(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.list_profile_videos(
+ username="creator",
+ )
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_profile_videos_with_all_params(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.list_profile_videos(
+ username="creator",
+ cursor="x",
+ region="US",
+ sort_by="latest",
+ )
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_profile_videos(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.tiktok.with_raw_response.list_profile_videos(
+ username="creator",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = await response.parse()
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_profile_videos(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.tiktok.with_streaming_response.list_profile_videos(
+ username="creator",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = await response.parse()
+ assert_matches_type(TiktokListProfileVideosResponse, tiktok, 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_list_profile_videos(self, async_client: AsyncInfluship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `username` but received ''"):
+ await async_client.raw.tiktok.with_raw_response.list_profile_videos(
+ username="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_video_comments(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_video_comments_with_all_params(self, async_client: AsyncInfluship) -> None:
+ tiktok = await async_client.raw.tiktok.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ cursor="x",
+ )
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_video_comments(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.tiktok.with_raw_response.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tiktok = await response.parse()
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_video_comments(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.tiktok.with_streaming_response.list_video_comments(
+ url="https://www.tiktok.com/@creator/video/7517114944362499342",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tiktok = await response.parse()
+ assert_matches_type(TiktokListVideoCommentsResponse, tiktok, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/raw/test_youtube.py b/tests/api_resources/raw/test_youtube.py
index ce9ad33..aaa36e4 100644
--- a/tests/api_resources/raw/test_youtube.py
+++ b/tests/api_resources/raw/test_youtube.py
@@ -11,6 +11,8 @@
from tests.utils import assert_matches_type
from influship.types.raw import (
YoutubeSearchResponse,
+ YoutubeGetVideoResponse,
+ YoutubeTypeaheadResponse,
YoutubeGetChannelResponse,
YoutubeGetTranscriptResponse,
YoutubeGetChannelTranscriptsResponse,
@@ -179,6 +181,48 @@ def test_path_params_get_transcript(self, client: Influship) -> None:
video_id="",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get_video(self, client: Influship) -> None:
+ youtube = client.raw.youtube.get_video(
+ "dQw4w9WgXcQ",
+ )
+ assert_matches_type(YoutubeGetVideoResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get_video(self, client: Influship) -> None:
+ response = client.raw.youtube.with_raw_response.get_video(
+ "dQw4w9WgXcQ",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ youtube = response.parse()
+ assert_matches_type(YoutubeGetVideoResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get_video(self, client: Influship) -> None:
+ with client.raw.youtube.with_streaming_response.get_video(
+ "dQw4w9WgXcQ",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ youtube = response.parse()
+ assert_matches_type(YoutubeGetVideoResponse, youtube, 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_video(self, client: Influship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"):
+ client.raw.youtube.with_raw_response.get_video(
+ "",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_search(self, client: Influship) -> None:
@@ -192,9 +236,14 @@ def test_method_search(self, client: Influship) -> None:
def test_method_search_with_all_params(self, client: Influship) -> None:
youtube = client.raw.youtube.search(
q="fitness workout",
+ content_type="videos",
country_code="US",
+ cursor="x",
+ duration="long",
language_code="en",
limit=20,
+ sort_by="popular",
+ upload_date="this_week",
)
assert_matches_type(YoutubeSearchResponse, youtube, path=["response"])
@@ -224,6 +273,50 @@ def test_streaming_response_search(self, client: Influship) -> None:
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_typeahead(self, client: Influship) -> None:
+ youtube = client.raw.youtube.typeahead(
+ q="x",
+ )
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_typeahead_with_all_params(self, client: Influship) -> None:
+ youtube = client.raw.youtube.typeahead(
+ q="x",
+ country_code="xx",
+ language_code="xx",
+ )
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_typeahead(self, client: Influship) -> None:
+ response = client.raw.youtube.with_raw_response.typeahead(
+ q="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ youtube = response.parse()
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_typeahead(self, client: Influship) -> None:
+ with client.raw.youtube.with_streaming_response.typeahead(
+ q="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ youtube = response.parse()
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
class TestAsyncYoutube:
parametrize = pytest.mark.parametrize(
@@ -387,6 +480,48 @@ async def test_path_params_get_transcript(self, async_client: AsyncInfluship) ->
video_id="",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get_video(self, async_client: AsyncInfluship) -> None:
+ youtube = await async_client.raw.youtube.get_video(
+ "dQw4w9WgXcQ",
+ )
+ assert_matches_type(YoutubeGetVideoResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get_video(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.youtube.with_raw_response.get_video(
+ "dQw4w9WgXcQ",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ youtube = await response.parse()
+ assert_matches_type(YoutubeGetVideoResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get_video(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.youtube.with_streaming_response.get_video(
+ "dQw4w9WgXcQ",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ youtube = await response.parse()
+ assert_matches_type(YoutubeGetVideoResponse, youtube, 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_video(self, async_client: AsyncInfluship) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"):
+ await async_client.raw.youtube.with_raw_response.get_video(
+ "",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_search(self, async_client: AsyncInfluship) -> None:
@@ -400,9 +535,14 @@ async def test_method_search(self, async_client: AsyncInfluship) -> None:
async def test_method_search_with_all_params(self, async_client: AsyncInfluship) -> None:
youtube = await async_client.raw.youtube.search(
q="fitness workout",
+ content_type="videos",
country_code="US",
+ cursor="x",
+ duration="long",
language_code="en",
limit=20,
+ sort_by="popular",
+ upload_date="this_week",
)
assert_matches_type(YoutubeSearchResponse, youtube, path=["response"])
@@ -431,3 +571,47 @@ async def test_streaming_response_search(self, async_client: AsyncInfluship) ->
assert_matches_type(YoutubeSearchResponse, youtube, path=["response"])
assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_typeahead(self, async_client: AsyncInfluship) -> None:
+ youtube = await async_client.raw.youtube.typeahead(
+ q="x",
+ )
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_typeahead_with_all_params(self, async_client: AsyncInfluship) -> None:
+ youtube = await async_client.raw.youtube.typeahead(
+ q="x",
+ country_code="xx",
+ language_code="xx",
+ )
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_typeahead(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.raw.youtube.with_raw_response.typeahead(
+ q="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ youtube = await response.parse()
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_typeahead(self, async_client: AsyncInfluship) -> None:
+ async with async_client.raw.youtube.with_streaming_response.typeahead(
+ q="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ youtube = await response.parse()
+ assert_matches_type(YoutubeTypeaheadResponse, youtube, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_creator_emails.py b/tests/api_resources/test_creator_emails.py
new file mode 100644
index 0000000..f1e6772
--- /dev/null
+++ b/tests/api_resources/test_creator_emails.py
@@ -0,0 +1,92 @@
+# 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 influship import Influship, AsyncInfluship
+from tests.utils import assert_matches_type
+from influship.types import CreatorEmailLookupResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCreatorEmails:
+ 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_lookup(self, client: Influship) -> None:
+ creator_email = client.creator_emails.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ )
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_lookup(self, client: Influship) -> None:
+ response = client.creator_emails.with_raw_response.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ creator_email = response.parse()
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_lookup(self, client: Influship) -> None:
+ with client.creator_emails.with_streaming_response.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ creator_email = response.parse()
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncCreatorEmails:
+ 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_lookup(self, async_client: AsyncInfluship) -> None:
+ creator_email = await async_client.creator_emails.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ )
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_lookup(self, async_client: AsyncInfluship) -> None:
+ response = await async_client.creator_emails.with_raw_response.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ creator_email = await response.parse()
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_lookup(self, async_client: AsyncInfluship) -> None:
+ async with async_client.creator_emails.with_streaming_response.lookup(
+ creators=[{"creator_id": "123e4567-e89b-12d3-a456-426614174000"}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ creator_email = await response.parse()
+ assert_matches_type(CreatorEmailLookupResponse, creator_email, path=["response"])
+
+ assert cast(Any, response.is_closed) is True