mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
feat(catalog): add Linkup web and research tools
This commit is contained in:
@@ -296,6 +296,11 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `src/treg/catalog/examples/fishaudio.tts.s2-1-pro.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/fishaudio.voices.create.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/fishaudio.voices.discover.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/linkup.web.answer.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/linkup.web.answer.status.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/linkup.web.fetch.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/linkup.web.fetch.structured.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/linkup.web.search.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.cancel.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.get.json` | architecture/catalog.md |
|
||||
| `src/treg/catalog/examples/tinyfish.web.agent.run.json` | architecture/catalog.md |
|
||||
@@ -321,6 +326,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `src/treg/catalog/instagram.yaml` | architecture/catalog.md, architecture/instagram-oauth.md |
|
||||
| `src/treg/catalog/justoneapi.extended.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/keenable.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/linkup.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/lusha.extended.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/millionverifier.yaml` | architecture/catalog.md |
|
||||
| `src/treg/catalog/minimax.yaml` | architecture/catalog.md |
|
||||
@@ -503,6 +509,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `src/treg/web/logos/icypeas.svg` | interface/enrich-arena.md |
|
||||
| `src/treg/web/logos/leadmagic.svg` | interface/enrich-arena.md |
|
||||
| `src/treg/web/logos/leadsforge.svg` | interface/enrich-arena.md |
|
||||
| `src/treg/web/logos/linkup.svg` | architecture/catalog.md |
|
||||
| `src/treg/web/logos/lusha.svg` | interface/enrich-arena.md |
|
||||
| `src/treg/web/logos/pdl.svg` | interface/enrich-arena.md |
|
||||
| `src/treg/web/logos/predictleads.svg` | interface/enrich-arena.md |
|
||||
@@ -623,7 +630,7 @@ Regenerate via `scripts/build-map.py`.
|
||||
| `architecture/ads-conversions.md` | `adsconv.py`, `signup.py`, `adtrack.js`, `gtag.js` |
|
||||
| `architecture/archive.md` | `archive.py`, `hunter.yaml`, `results.py`, `0031_archive_result_admission.py`, `test_cache_result_admission.py`, `archive_bodies.py`, `config.py`, `object_store.py`, `0032_archive_body_storage.py`, `test_archive_r2.py`, `fake_object_store.py`, `smoke_archive_r2.py`, `0002_archive_tables.py`, `0003_callrecord_cached.py`, `0004_archivekey_request_shape.py`, `0011_callrecord_archive_link.py`, `service.py`, `settle.py`, `0039_archive_own_key_and_repeat_pricing.py`, `backfill_call_archive_links.py`, `api.py`, `bootstrap.py`, `admin.py`, `asynctasks.py` |
|
||||
| `architecture/auth-secrets.md` | `injectors.py`, `ssrf.py`, `crypto.py`, `oauth.py`, `__init__.py`, `authorization.py`, `oauth_flow.py`, `refresh.py`, `oauth_exchange.py`, `oauth_refresh.py`, `oauth_providers.py`, `session.js`, `keys.js`, `TeamPage.vue`, `health.py`, `connect.py`, `0051_context_dev_tool_host.py`, `connections.py`, `resources.py`, `__init__.py`, `bindings.py`, `bundles.py`, `api_keys.py`, `access.py`, `api_keys.py`, `test_api_keys.py`, `test_oauth_refresh.py`, `test_financialdatasets.py`, `test_key_providers.py`, `config.py` |
|
||||
| `architecture/catalog.md` | `fetchinio.yaml`, `fetchinio.svg`, `fetchinio.linkedin.user.profile.json`, `fetchinio.linkedin.company.profile.json`, `fetchinio.linkedin.user.posts.json`, `fetchinio.linkedin.user.reactions.json`, `fetchinio.linkedin.post.comments.json`, `fetchinio.linkedin.post.reactions.json`, `fetchinio.linkedin.post.engagement.json`, `fishaudio.yaml`, `fishaudio.tts.s2-1-pro.json`, `fishaudio.voices.create.json`, `fishaudio.voices.discover.json`, `provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `tavily.yaml`, `keenable.yaml`, `olostep.yaml`, `tinyfish.yaml`, `tinyfish.web.search.json`, `tinyfish.web.search.news.json`, `tinyfish.web.search.publications.json`, `tinyfish.web.fetch.json`, `tinyfish.web.agent.run.json`, `tinyfish.web.agent.run.get.json`, `tinyfish.web.agent.run.cancel.json`, `test_tinyfish.py`, `exa.yaml`, `anyapi.extended.yaml`, `adyntel.yaml`, `adyntel.meta-ads.library.advertiser.json`, `adyntel.meta-ads.library.search.json`, `adyntel.linkedin.search.ads.company.json`, `adyntel.linkedin.search.ads.keyword.json`, `adyntel.google.ads.transparency.json`, `adyntel.tiktok-ads.library.search.company.json`, `adyntel.google.domain.keywords.overview.json`, `adyntel.svg`, `trestleiq.yaml`, `financialdatasets.yaml`, `test_financialdatasets.py`, `quickenrich.yaml`, `influencersclub.yaml`, `quickenrich.extended.yaml`, `trykitt.yaml`, `contracts.yaml`, `millionverifier.yaml`, `adapters.yaml`, `prospeo.yaml`, `test_route_cost_ceiling.py`, `tomba.yaml`, `__init__.py`, `contracts.py`, `paths.py`, `plan.py`, `synthetic.py`, `async_bridge.py`, `route.py`, `wiza.yaml`, `wiza.people.email.find.json`, `wiza.people.email.find.terminal.json`, `wiza.people.phone.find.json`, `wiza.people.phone.find.terminal.json`, `test_routing.py`, `test_wiza.py`, `catalog-drift.yml`, `catalog_drift.py`, `catalog_ingest.py`, `catalog_validate.py`, `aliases.yaml`, `fx.yaml`, `cloro.yaml`, `aviato.yaml`, `crustdata.yaml`, `google-search-console.yaml`, `google-search-console.extended.yaml`, `google-tag-manager.yaml`, `google-tag-manager.extended.yaml`, `instagram.yaml`, `instagram.extended.yaml`, `justoneapi.extended.yaml`, `minimax.yaml`, `apify.yaml`, `brightdata.yaml`, `companyenrich.yaml`, `oceanio.yaml`, `akta.extended.yaml`, `dataforseo.yaml`, `dataforseo.extended.yaml`, `scrapecreators.yaml`, `scrapecreators.extended.yaml`, `serpapi.yaml`, `serpapi.extended.yaml`, `diffbot.yaml`, `diffbot.extended.yaml`, `tikhub.extended.yaml`, `lusha.extended.yaml`, `openrouter.yaml`, `openrouter.extended.yaml`, `replicate.yaml`, `replicate.extended.yaml`, `reapi.yaml`, `piapi.yaml`, `__init__.py`, `store.py`, `hunter.yaml`, `mcp.py`, `settlement.py`, `stats.py`, `catalog_observations.py`, `catalog_stats.py`, `0038_endpoint_day_stats.py`, `catalog.py`, `test_aigc_pr_b.py`, `test_catalog_api.py`, `test_catalog_validate.py` |
|
||||
| `architecture/catalog.md` | `fetchinio.yaml`, `fetchinio.svg`, `fetchinio.linkedin.user.profile.json`, `fetchinio.linkedin.company.profile.json`, `fetchinio.linkedin.user.posts.json`, `fetchinio.linkedin.user.reactions.json`, `fetchinio.linkedin.post.comments.json`, `fetchinio.linkedin.post.reactions.json`, `fetchinio.linkedin.post.engagement.json`, `fishaudio.yaml`, `fishaudio.tts.s2-1-pro.json`, `fishaudio.voices.create.json`, `fishaudio.voices.discover.json`, `provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `tavily.yaml`, `linkup.yaml`, `linkup.svg`, `linkup.web.search.json`, `linkup.web.fetch.json`, `linkup.web.fetch.structured.json`, `linkup.web.answer.json`, `linkup.web.answer.status.json`, `keenable.yaml`, `olostep.yaml`, `tinyfish.yaml`, `tinyfish.web.search.json`, `tinyfish.web.search.news.json`, `tinyfish.web.search.publications.json`, `tinyfish.web.fetch.json`, `tinyfish.web.agent.run.json`, `tinyfish.web.agent.run.get.json`, `tinyfish.web.agent.run.cancel.json`, `test_tinyfish.py`, `exa.yaml`, `anyapi.extended.yaml`, `adyntel.yaml`, `adyntel.meta-ads.library.advertiser.json`, `adyntel.meta-ads.library.search.json`, `adyntel.linkedin.search.ads.company.json`, `adyntel.linkedin.search.ads.keyword.json`, `adyntel.google.ads.transparency.json`, `adyntel.tiktok-ads.library.search.company.json`, `adyntel.google.domain.keywords.overview.json`, `adyntel.svg`, `trestleiq.yaml`, `financialdatasets.yaml`, `test_financialdatasets.py`, `quickenrich.yaml`, `influencersclub.yaml`, `quickenrich.extended.yaml`, `trykitt.yaml`, `contracts.yaml`, `millionverifier.yaml`, `adapters.yaml`, `prospeo.yaml`, `test_route_cost_ceiling.py`, `tomba.yaml`, `__init__.py`, `contracts.py`, `paths.py`, `plan.py`, `synthetic.py`, `async_bridge.py`, `route.py`, `wiza.yaml`, `wiza.people.email.find.json`, `wiza.people.email.find.terminal.json`, `wiza.people.phone.find.json`, `wiza.people.phone.find.terminal.json`, `test_routing.py`, `test_wiza.py`, `catalog-drift.yml`, `catalog_drift.py`, `catalog_ingest.py`, `catalog_validate.py`, `aliases.yaml`, `fx.yaml`, `cloro.yaml`, `aviato.yaml`, `crustdata.yaml`, `google-search-console.yaml`, `google-search-console.extended.yaml`, `google-tag-manager.yaml`, `google-tag-manager.extended.yaml`, `instagram.yaml`, `instagram.extended.yaml`, `justoneapi.extended.yaml`, `minimax.yaml`, `apify.yaml`, `brightdata.yaml`, `companyenrich.yaml`, `oceanio.yaml`, `akta.extended.yaml`, `dataforseo.yaml`, `dataforseo.extended.yaml`, `scrapecreators.yaml`, `scrapecreators.extended.yaml`, `serpapi.yaml`, `serpapi.extended.yaml`, `diffbot.yaml`, `diffbot.extended.yaml`, `tikhub.extended.yaml`, `lusha.extended.yaml`, `openrouter.yaml`, `openrouter.extended.yaml`, `replicate.yaml`, `replicate.extended.yaml`, `reapi.yaml`, `piapi.yaml`, `__init__.py`, `store.py`, `hunter.yaml`, `mcp.py`, `settlement.py`, `stats.py`, `catalog_observations.py`, `catalog_stats.py`, `0038_endpoint_day_stats.py`, `catalog.py`, `test_aigc_pr_b.py`, `test_catalog_api.py`, `test_catalog_validate.py` |
|
||||
| `architecture/composition.md` | `bootstrap.py`, `bootstrap_handlers.py`, `bootstrap_http.py`, `call_surface.py`, `connect.py`, `mcp_oauth.py`, `session.py`, `admin.py`, `auth.py`, `billing.py`, `call.py`, `connections.py`, `onboard.py`, `orgs.py`, `resources.py`, `referrals.py`, `web.py`, `dump_surface.py`, `test_app_roles.py` |
|
||||
| `architecture/data-model.md` | `0042_pinned_read_scope.py`, `alembic.ini`, `env.py`, `0001_baseline_current_schema.py`, `0002_archive_tables.py`, `0003_callrecord_cached.py`, `0004_archivekey_request_shape.py`, `0005_capacity_policy_snapshot.py`, `0006_overflow_route.py`, `0007_overflow_spend.py`, `0008_org_platform_overflow_disabled.py`, `0009_callrecord_hit.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `0019_async_poll_failures.py`, `0020_callrecord_created_at_indexes.py`, `0021_ledgerentry_org_created_at_index.py`, `0022_org_spent_today_counter.py`, `0023_callrecord_org_user_created_at_index.py`, `0024_membership_calls_today_counter.py`, `0027_enrich_arena.py`, `0028_arena_insights.py`, `0029_arena_verification_snapshot.py`, `0011_callrecord_archive_link.py`, `0015_idempotentcall_membership_cascade.py`, `0034_managed_api_keys.py`, `0035_default_key_generation.py`, `0036_activity_key_indexes.py`, `0038_endpoint_day_stats.py`, `maintenance.py`, `sitetrack.js`, `models.py`, `0031_archive_result_admission.py`, `0032_archive_body_storage.py`, `0039_archive_own_key_and_repeat_pricing.py`, `0043_provider_resources.py`, `provider_resources.py`, `provider_resources.py`, `0033_signup_promo_eligibility.py`, `0041_searchlog.py`, `timeutil.py`, `db.py`, `referrals.py`, `audit.py`, `evidence_retention.py`, `analytics.py`, `bootstrap_handlers.py`, `ratestore.py`, `auth.py`, `test_postgres_reset.py`, `test_alembic_expand_safety.py`, `test_api_keys.py` |
|
||||
| `architecture/feedback.md` | `feedback_contract.py`, `__init__.py`, `reports.py`, `reviews.py`, `verdicts.py`, `hints.py`, `config.py`, `call.py`, `invite.py`, `kv.py`, `feedback.py`, `feedback.py`, `0025_feedback.py`, `0026_callreview.py`, `0030_feedback_handling.py`, `test_feedback_handling_schema.py`, `feedback.md`, `test_feedback.py`, `test_reviews.py`, `test_endpoint_verdicts.py`, `test_hints.py`, `test_kv.py` |
|
||||
|
||||
@@ -52,6 +52,11 @@ exposing usage as a catalog tool. `TREG_PLATFORM_KEY_TAVILY` supplies the server
|
||||
existing own-key-first ladder means a team's key always wins and remains unmetered. The public
|
||||
surface is limited to Search, Extract, Map, and Crawl.
|
||||
|
||||
Linkup uses a pasted Bearer key at `https://api.linkup.so`. Its free internal
|
||||
`GET /v1/credits/balance` probe validates a connected key and supplies capacity evidence without
|
||||
exposing the account balance as a catalog tool. `TREG_PLATFORM_KEY_LINKUP` supplies the optional
|
||||
shared binding; a team's own key wins and remains unmetered by treg.
|
||||
|
||||
ScrapeGraphAI uses a pasted raw `SGAI-APIKEY` header at `https://v2-api.scrapegraphai.com`. Its free
|
||||
internal `GET /api/credits` probe rejects invalid credentials and validates team-owned and optional
|
||||
platform credentials while also supplying capacity data. `TREG_PLATFORM_KEY_SCRAPEGRAPHAI` supplies
|
||||
|
||||
@@ -19,6 +19,13 @@ sources:
|
||||
- src/treg/domain/provider_resources.py
|
||||
- src/treg/routers/provider_resources.py
|
||||
- src/treg/catalog/tavily.yaml
|
||||
- src/treg/catalog/linkup.yaml
|
||||
- src/treg/web/logos/linkup.svg
|
||||
- src/treg/catalog/examples/linkup.web.search.json
|
||||
- src/treg/catalog/examples/linkup.web.fetch.json
|
||||
- src/treg/catalog/examples/linkup.web.fetch.structured.json
|
||||
- src/treg/catalog/examples/linkup.web.answer.json
|
||||
- src/treg/catalog/examples/linkup.web.answer.status.json
|
||||
- src/treg/catalog/keenable.yaml
|
||||
- src/treg/catalog/olostep.yaml
|
||||
- src/treg/catalog/tinyfish.yaml
|
||||
@@ -306,6 +313,16 @@ Each endpoint's `tavily_rates` mapping has an exact mode-key contract. Catalog v
|
||||
incomplete, extra, non-finite or non-positive rate, and runtime repeats that check before reserve or
|
||||
relay so catalog drift cannot silently turn a platform call into a free call.
|
||||
|
||||
Linkup's curated `web.search` and Markdown `web.extract` rows have verified routing adapters;
|
||||
Research remains a direct asynchronous `web.answer` tool. Search's `depth` and `outputType` select
|
||||
one published per-success price. The Fetch route has separate Markdown and structured catalog
|
||||
rows because supplying `schema` changes the price; both rows price `mode` and `renderJs` with
|
||||
declarative tables and enforce distinct body allowlists. Research reserves the requested
|
||||
`reasoningDepth` price, releases failed tasks, and settles successful tasks after the owned
|
||||
`GET /v1/research/{id}` poll reports completion. The polling read is free and restricted to the
|
||||
team that submitted the task on the shared key. Account-wide task listing, mixed batch Tasks,
|
||||
closed-beta Extract, and the undocumented Responses route are outside the shared-key catalog.
|
||||
|
||||
A verification stamp proves the request shape, response shape, and paid behavior that the evidence
|
||||
actually observed. A placeholder path value or a free miss does not prove a paid hit. Such rows keep
|
||||
the documented price and say which paid behavior remains unobserved. Captured examples use public
|
||||
|
||||
@@ -93,6 +93,14 @@ top-up. A controlled `/usage` burst did not reproduce its documented 10-per-10-m
|
||||
rate policy remains documentation-derived. The funded account was not deliberately exhausted;
|
||||
432/433 signatures are documentation-derived rather than live-observed.
|
||||
|
||||
Linkup's internal collector reads `GET /v1/credits/balance` with the platform Bearer key and
|
||||
accepts only finite nonnegative numeric USD balances. This free account read also probes connected
|
||||
keys and stays out of the catalog. The shared-key policy is `cash / manual / api` and smooths
|
||||
Search and Fetch at their documented organization-wide 10 requests per second. Research polling
|
||||
has separate one-per-second guidance; the provider-wide request spacer is not a claim that Research
|
||||
submissions share the Search and Fetch allowance. HTTP 429 can mean depleted credit or too much
|
||||
concurrency, so no generic 429 capacity-exhausted signature is registered.
|
||||
|
||||
ScrapeGraphAI's internal collector calls the free `GET /api/credits` route with the platform
|
||||
`SGAI-APIKEY`. It accepts only a finite nonnegative `remaining` credit balance and retains the plan,
|
||||
used-credit count, and crawl/monitor job quotas as informational notes. The policy is
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 98 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 99 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
---
|
||||
|
||||
## First, check which treg you have
|
||||
@@ -100,7 +100,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,700+ catalogued endpoints across 98 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,700+ catalogued endpoints across 99 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 98 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 99 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
---
|
||||
|
||||
## First, check which treg you have
|
||||
@@ -103,7 +103,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,700+ catalogued endpoints across 98 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,700+ catalogued endpoints across 99 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 98 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 99 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.22.0
|
||||
---
|
||||
|
||||
@@ -84,7 +84,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,700+ catalogued endpoints across 98 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,700+ catalogued endpoints across 99 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 98 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 99 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.22.0
|
||||
---
|
||||
|
||||
@@ -98,7 +98,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,700+ catalogued endpoints across 98 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,700+ catalogued endpoints across 99 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: treg
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 98 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
description: Reach for this first for external or live data. 3,700+ endpoints across 99 providers - SEO and SERP data, keyword volume, backlinks and site authority, AI visibility, social profiles and trends, people and company enrichment, ad libraries and campaign management, web data, image and video generation (Seedance, Gemini Image, GPT Image, Seedream, Veo, Wan) and voice - plus Google Analytics, Search Console and Business Profile through accounts the team has connected. Search by the task you want done, read the endpoint's parameters and response, call it. Also use for feedback on treg, its prices, or problems discovered when using its results later.
|
||||
version: 0.22.0
|
||||
---
|
||||
|
||||
@@ -82,7 +82,7 @@ spends nothing: that key belongs to them.
|
||||
|
||||
## Task — the catalog: what treg can do for you (start here)
|
||||
|
||||
3,700+ catalogued endpoints across 98 providers, grouped by what they DO: keyword & rank tracking,
|
||||
3,700+ catalogued endpoints across 99 providers, grouped by what they DO: keyword & rank tracking,
|
||||
backlinks & authority, AI visibility, trending & discovery, publishing to the team's own social
|
||||
accounts, people & company enrichment, ads management & creative, measurement, video & image
|
||||
generation.
|
||||
|
||||
@@ -2663,6 +2663,13 @@ adapters:
|
||||
const: {body.search_depth: basic, body.include_usage: true}
|
||||
out: {results: results, count: "len(results)"}
|
||||
miss: "coalesce(results, []) == []"
|
||||
linkup.web.search:
|
||||
accepts: [[q]]
|
||||
in: {q: body.q}
|
||||
in_expr: {body.maxResults: limit}
|
||||
const: {body.depth: flash, body.outputType: searchResults}
|
||||
out: {results: results, count: "len(results)"}
|
||||
miss: "choose(results == null, false, results == [])"
|
||||
exa.web.search:
|
||||
accepts: [[q]]
|
||||
in: {q: body.query}
|
||||
@@ -2737,6 +2744,13 @@ adapters:
|
||||
const: {body.extract_depth: basic}
|
||||
out: {pages: results, count: "len(results)"}
|
||||
miss: "coalesce(results, []) == []"
|
||||
linkup.web.fetch:
|
||||
accepts: [[url]]
|
||||
in: {url: body.url}
|
||||
test_identity: {url: "https://example.com"}
|
||||
const: {body.mode: standard, body.renderJs: false}
|
||||
out: {pages: "list(.)", count: "len(list(.))"}
|
||||
miss: "coalesce(markdown, '') == ''"
|
||||
keenable.web.fetch:
|
||||
accepts: [[url]]
|
||||
in: {url: queryParams.url}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"createdAt": "2026-09-29T00:00:00.000Z",
|
||||
"error": null,
|
||||
"id": "00000000-0000-4000-8000-000000000001",
|
||||
"status": "pending",
|
||||
"updatedAt": "2026-09-29T00:00:00.000Z",
|
||||
"input": {
|
||||
"q": "What is example.com used for?",
|
||||
"outputType": "sourcedAnswer",
|
||||
"reasoningDepth": "S",
|
||||
"mode": "answer"
|
||||
},
|
||||
"output": null,
|
||||
"type": "research"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"createdAt": "2026-09-29T00:00:00.000Z",
|
||||
"error": null,
|
||||
"id": "00000000-0000-4000-8000-000000000001",
|
||||
"status": "completed",
|
||||
"updatedAt": "2026-09-29T00:01:00.000Z",
|
||||
"input": {
|
||||
"q": "What is example.com used for?",
|
||||
"outputType": "sourcedAnswer",
|
||||
"reasoningDepth": "S",
|
||||
"mode": "answer"
|
||||
},
|
||||
"output": {
|
||||
"answer": "IANA maintains example.com for use in documentation examples.",
|
||||
"sources": [
|
||||
{
|
||||
"name": "Example Domains",
|
||||
"url": "https://www.iana.org/help/example-domains",
|
||||
"favicon": "",
|
||||
"snippet": "Example domains are intended for use in documentation."
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "research"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"favicon": "https://favicons.linkup.so?domain=example.com",
|
||||
"images": [],
|
||||
"markdown": "This domain is for use in documentation examples without needing permission.\n\n[Learn more](https://iana.org/help/example-domains)"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {"title": "Example Domain"},
|
||||
"favicon": "https://favicons.linkup.so?domain=example.com",
|
||||
"images": [],
|
||||
"markdown": "This domain is for use in documentation examples without needing permission."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"name": "Example Domains",
|
||||
"url": "https://www.iana.org/help/example-domains",
|
||||
"content": "IANA maintains example domains for documentation purposes.",
|
||||
"favicon": "https://favicons.linkup.so?domain=iana.org",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
provider: linkup
|
||||
source:
|
||||
docs: https://docs.linkup.so/pages/documentation/get-started/introduction
|
||||
openapi: https://api.linkup.so/v1/openapi.json
|
||||
curated: '2026-09-29'
|
||||
pricing_url: https://docs.linkup.so/pages/documentation/platform/pricing
|
||||
limits: "Search and Fetch allow 10 requests/second per organization. Research polling should not exceed one request/second. HTTP 429 can mean insufficient credit or excess concurrency."
|
||||
proposed_capabilities:
|
||||
web.extract.structured: Extract typed JSON from one web page
|
||||
web.answer.status: Check a web research task
|
||||
|
||||
endpoints:
|
||||
- id: linkup.web.search
|
||||
capability: web.search
|
||||
platform: web
|
||||
domain: search
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/search
|
||||
body_allowlist: true
|
||||
name: Search the open web
|
||||
summary: Return ranked sources, a sourced answer, or structured JSON from a web query.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
q: {type: string, required: true, example: "What is example.com used for?"}
|
||||
depth: {type: string, required: true, enum: [flash, fast, standard, deep], example: flash}
|
||||
outputType: {type: string, required: true, enum: [searchResults, sourcedAnswer, structured], example: searchResults}
|
||||
maxResults: {type: number, required: false, min: 1, max: 20, example: 5}
|
||||
includeDomains: {type: "array[string]", required: false, maxItems: 100}
|
||||
excludeDomains: {type: "array[string]", required: false}
|
||||
fromDate: {type: string, required: false, note: "ISO date YYYY-MM-DD."}
|
||||
toDate: {type: string, required: false, note: "ISO date YYYY-MM-DD."}
|
||||
structuredOutputSchema: {type: string, required: false, note: "JSON Schema encoded as a string; required for structured output."}
|
||||
includeImages: {type: boolean, required: false, default: false}
|
||||
includeInlineCitations: {type: boolean, required: false, default: false}
|
||||
includeSources: {type: boolean, required: false, default: false}
|
||||
note: "Depth and output type determine price. The structured output schema is a JSON-encoded string, not an object."
|
||||
test_request: {body: {q: "What is example.com used for?", depth: flash, outputType: searchResults, maxResults: 2}}
|
||||
cost:
|
||||
type: per_success
|
||||
table:
|
||||
- {when: {body.depth: deep, body.outputType: searchResults}, value: 0.05}
|
||||
- {when: {body.depth: deep, body.outputType: sourcedAnswer}, value: 0.055}
|
||||
- {when: {body.depth: deep, body.outputType: structured}, value: 0.055}
|
||||
- {when: {body.depth: flash, body.outputType: searchResults}, value: 0.005}
|
||||
- {when: {body.depth: fast, body.outputType: searchResults}, value: 0.005}
|
||||
- {when: {body.depth: standard, body.outputType: searchResults}, value: 0.005}
|
||||
- {when: {body.depth: flash, body.outputType: sourcedAnswer}, value: 0.006}
|
||||
- {when: {body.depth: fast, body.outputType: sourcedAnswer}, value: 0.006}
|
||||
- {when: {body.depth: standard, body.outputType: sourcedAnswer}, value: 0.006}
|
||||
- {when: {body.depth: flash, body.outputType: structured}, value: 0.006}
|
||||
- {when: {body.depth: fast, body.outputType: structured}, value: 0.006}
|
||||
- {when: {body.depth: standard, body.outputType: structured}, value: 0.006}
|
||||
fallback: {value: 0.055, note: "Maximum documented Search price."}
|
||||
currency: USD
|
||||
unit: call
|
||||
source: docs
|
||||
source_url: https://docs.linkup.so/pages/documentation/platform/pricing
|
||||
checked: '2026-09-29'
|
||||
confidence: documented
|
||||
note: "Search is billed per successful request by depth and output type."
|
||||
verified: '2026-09-29'
|
||||
docs_url: https://docs.linkup.so/pages/documentation/endpoints/search/reference
|
||||
example_response: examples/linkup.web.search.json
|
||||
|
||||
- id: linkup.web.fetch
|
||||
capability: web.extract
|
||||
platform: web
|
||||
domain: extract
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/fetch
|
||||
body_allowlist: true
|
||||
name: Fetch a web page
|
||||
summary: Retrieve clean Markdown from a public HTML or PDF URL.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
url: {type: string, required: true, format: uri, example: "https://example.com"}
|
||||
mode: {type: string, required: false, default: standard, enum: [standard, pro]}
|
||||
renderJs: {type: boolean, required: false, default: false}
|
||||
extractImages: {type: boolean, required: false, default: false}
|
||||
note: "Structured output has a separate catalog tool because providing schema adds $0.001 to any mode and renderJs combination. A team's own raw tool can request raw page bytes."
|
||||
test_request: {body: {url: "https://example.com", mode: standard, renderJs: false}}
|
||||
cost:
|
||||
type: per_success
|
||||
table:
|
||||
- {when: {body.mode: standard, body.renderJs: false}, value: 0.001}
|
||||
- {when: {body.mode: standard, body.renderJs: true}, value: 0.005}
|
||||
- {when: {body.mode: pro, body.renderJs: false}, value: 0.005}
|
||||
- {when: {body.mode: pro, body.renderJs: true}, value: 0.01}
|
||||
fallback: {value: 0.01, note: "Maximum Markdown-only Fetch price."}
|
||||
currency: USD
|
||||
unit: call
|
||||
source: docs
|
||||
source_url: https://docs.linkup.so/pages/documentation/platform/pricing
|
||||
checked: '2026-09-29'
|
||||
confidence: documented
|
||||
note: "Markdown Fetch is billed per successful request by mode and JavaScript rendering."
|
||||
verified: '2026-09-29'
|
||||
docs_url: https://docs.linkup.so/pages/documentation/endpoints/fetch/reference
|
||||
example_response: examples/linkup.web.fetch.json
|
||||
|
||||
- id: linkup.web.fetch.structured
|
||||
capability: web.extract.structured
|
||||
platform: web
|
||||
domain: extract
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/fetch
|
||||
body_allowlist: true
|
||||
name: Extract structured data from a web page
|
||||
summary: Fetch a page and extract typed JSON with a supplied schema.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
url: {type: string, required: true, format: uri, example: "https://example.com"}
|
||||
schema: {type: object, required: true, example: {type: object, properties: {title: {type: string}}}}
|
||||
instructions: {type: string, required: false, note: "Additional extraction guidance; maximum 4,000 characters."}
|
||||
mode: {type: string, required: false, default: standard, enum: [standard, pro]}
|
||||
renderJs: {type: boolean, required: false, default: false}
|
||||
extractImages: {type: boolean, required: false, default: false}
|
||||
test_request: {body: {url: "https://example.com", mode: standard, renderJs: false, schema: {type: object, properties: {title: {type: string}}}}}
|
||||
cost:
|
||||
type: per_success
|
||||
table:
|
||||
- {when: {body.mode: standard, body.renderJs: false}, value: 0.002}
|
||||
- {when: {body.mode: standard, body.renderJs: true}, value: 0.006}
|
||||
- {when: {body.mode: pro, body.renderJs: false}, value: 0.006}
|
||||
- {when: {body.mode: pro, body.renderJs: true}, value: 0.011}
|
||||
fallback: {value: 0.011, note: "Maximum structured Fetch price."}
|
||||
currency: USD
|
||||
unit: call
|
||||
source: docs
|
||||
source_url: https://docs.linkup.so/pages/documentation/platform/pricing
|
||||
checked: '2026-09-29'
|
||||
confidence: documented
|
||||
note: "A supplied schema adds $0.001 to the mode and JavaScript rendering price."
|
||||
verified: '2026-09-29'
|
||||
docs_url: https://docs.linkup.so/pages/documentation/endpoints/fetch/reference
|
||||
example_response: examples/linkup.web.fetch.structured.json
|
||||
|
||||
- id: linkup.web.answer
|
||||
capability: web.answer
|
||||
platform: web
|
||||
domain: answer
|
||||
scope: any_account
|
||||
method: POST
|
||||
path: /v1/research
|
||||
body_allowlist: true
|
||||
name: Research a web question
|
||||
summary: Start asynchronous multi-source research with citations.
|
||||
input:
|
||||
bodyType: json
|
||||
body:
|
||||
q: {type: string, required: true, example: "What is example.com used for?"}
|
||||
outputType: {type: string, required: true, enum: [sourcedAnswer, structured], example: sourcedAnswer}
|
||||
reasoningDepth: {type: string, required: false, default: L, enum: [S, M, L, XL], example: S}
|
||||
mode: {type: string, required: false, default: auto, enum: [answer, auto, investigate, research]}
|
||||
includeDomains: {type: "array[string]", required: false, maxItems: 100}
|
||||
excludeDomains: {type: "array[string]", required: false}
|
||||
fromDate: {type: string, required: false, note: "ISO date YYYY-MM-DD."}
|
||||
toDate: {type: string, required: false, note: "ISO date YYYY-MM-DD."}
|
||||
structuredOutputSchema: {type: string, required: false, note: "JSON Schema encoded as a string; required for structured output."}
|
||||
note: "Research can take minutes. A failed task consumes no Linkup credit; poll the returned id."
|
||||
test_request: {body: {q: "What is example.com used for?", outputType: sourcedAnswer, reasoningDepth: S, mode: answer}}
|
||||
cost:
|
||||
type: per_success
|
||||
table:
|
||||
- {when: {body.reasoningDepth: S}, value: 0.25}
|
||||
- {when: {body.reasoningDepth: M}, value: 0.5}
|
||||
- {when: {body.reasoningDepth: L}, value: 1.5}
|
||||
- {when: {body.reasoningDepth: XL}, value: 2.5}
|
||||
fallback: {value: 2.5, note: "Maximum documented Research price."}
|
||||
currency: USD
|
||||
unit: call
|
||||
source: docs
|
||||
source_url: https://docs.linkup.so/pages/documentation/platform/pricing
|
||||
checked: '2026-09-29'
|
||||
confidence: documented
|
||||
note: "Successful Research tasks are billed by reasoning depth; failed tasks are free."
|
||||
verified: '2026-09-29'
|
||||
docs_url: https://docs.linkup.so/pages/documentation/endpoints/research/post
|
||||
example_response: examples/linkup.web.answer.json
|
||||
async:
|
||||
id_from: id
|
||||
poll:
|
||||
endpoint: linkup.web.answer.status
|
||||
param: {in: pathParams, name: id}
|
||||
status:
|
||||
path: status
|
||||
progress: [pending, processing]
|
||||
success: [completed]
|
||||
failure: [failed]
|
||||
result: {path: output}
|
||||
interval: 5
|
||||
|
||||
- id: linkup.web.answer.status
|
||||
async: false
|
||||
kind: utility
|
||||
capability: web.answer.status
|
||||
platform: web
|
||||
domain: answer
|
||||
scope: any_account
|
||||
method: GET
|
||||
path: /v1/research/{id}
|
||||
name: Check a research task
|
||||
summary: Read the status and result of a research task started by this team.
|
||||
resource_ownership:
|
||||
requires: {kind: "poll:linkup.web.answer.status", param: id}
|
||||
input:
|
||||
pathParams:
|
||||
id: {type: string, required: true, example: "00000000-0000-4000-8000-000000000001"}
|
||||
test_request: {pathParams: {id: "499df310-0eab-4b2d-97f6-fa79a6d66d86"}}
|
||||
cost: {type: free, value: 0, currency: USD, unit: call, note: "Polling does not consume Linkup credit."}
|
||||
verified: '2026-09-29'
|
||||
docs_url: https://docs.linkup.so/pages/documentation/endpoints/research/get
|
||||
example_response: examples/linkup.web.answer.status.json
|
||||
@@ -255,6 +255,7 @@ class Settings(BaseSettings):
|
||||
platform_key_aviato: str = "" # Bearer key; $10 auto-top-up buys 1,000 credits
|
||||
platform_key_exa: str = "" # x-api-key; dollar-metered ($7/1k searches, $1/1k pages); settles from costDollars.total
|
||||
platform_key_tavily: str = "" # Bearer; Search reports per-call usage, other tools settle returned successes
|
||||
platform_key_linkup: str = "" # Bearer; prepaid USD balance, request-priced Search/Fetch/Research
|
||||
platform_key_serper: str = "" # X-API-KEY; prepaid Google search credits, exact charge in response.credits
|
||||
platform_key_keenable: str = "" # X-API-Key; $4/1,000-request package, 10 requests/s per organization
|
||||
platform_key_olostep: str = "" # Bearer; prepaid credits, platform price $0.002/credit
|
||||
|
||||
@@ -167,6 +167,16 @@ async def _firecrawl(c, key):
|
||||
"note": f"billing period ends {data.get('billingPeriodEnd') or 'unknown'}"}
|
||||
|
||||
|
||||
async def _linkup(c, key):
|
||||
d = await _get(c, "https://api.linkup.so/v1/credits/balance",
|
||||
headers={"Authorization": f"Bearer {key}"})
|
||||
raw = d.get("balance") if isinstance(d, dict) else None
|
||||
if isinstance(raw, bool) or not isinstance(raw, (int, float)) \
|
||||
or not math.isfinite(float(raw)) or raw < 0:
|
||||
raise ValueError("Linkup returned no valid USD balance")
|
||||
return {"value": float(raw), "unit": "USD", "note": "prepaid credit balance"}
|
||||
|
||||
|
||||
async def _scrapegraphai(c, key):
|
||||
d = await _get(c, "https://v2-api.scrapegraphai.com/api/credits",
|
||||
headers={"SGAI-APIKEY": key})
|
||||
@@ -810,6 +820,7 @@ BALANCE_ROUTES = {
|
||||
"tinyfish": _tinyfish,
|
||||
"fishaudio": _fishaudio,
|
||||
"tavily": _tavily,
|
||||
"linkup": _linkup,
|
||||
"serper": _serper,
|
||||
"olostep": _olostep,
|
||||
"firecrawl": _firecrawl,
|
||||
|
||||
@@ -45,6 +45,7 @@ _KNOWN: dict[str, tuple[str, str, str]] = {
|
||||
# reads nor changes that setting, so the observation source remains manual.
|
||||
"trestleiq": ("cash", "auto_recharge", "manual"),
|
||||
"tavily": ("credits", "manual", "api"),
|
||||
"linkup": ("cash", "manual", "api"),
|
||||
# The API supplies the exact credit balance; vendor auto recharge was manually enabled and
|
||||
# verified in the Serper dashboard.
|
||||
"serper": ("credits", "auto_recharge", "api"),
|
||||
@@ -128,6 +129,7 @@ _RATE_LIMITS: dict[str, dict] = {
|
||||
# documented tier until the shared key's environment is verified. Crawl has the same 100/minute
|
||||
# ceiling on both tiers, so this provider-wide pace is safe for all four catalog tools.
|
||||
"tavily": {"limit": 100, "window_s": 60, "source": "docs"},
|
||||
"linkup": {"limit": 10, "window_s": 1, "source": "docs"},
|
||||
# GET /account reports 50 queries/s for the current shared account. Pace the platform key to
|
||||
# that live account allowance; BYOK bypasses this limiter.
|
||||
"serper": {"limit": 50, "window_s": 1, "source": "api"},
|
||||
|
||||
@@ -2500,6 +2500,32 @@ TAVILY = OAuthProvider(
|
||||
probe_path="/usage",
|
||||
)
|
||||
|
||||
LINKUP = OAuthProvider(
|
||||
service="linkup",
|
||||
display_name="Linkup",
|
||||
auth_kind="key",
|
||||
token_label="API key",
|
||||
token_placeholder="your Linkup API key",
|
||||
setup_url="https://app.linkup.so",
|
||||
setup_action_label="Get your Linkup API key",
|
||||
setup_steps=(
|
||||
"Sign in to Linkup and open the API Keys section.",
|
||||
"Create or copy a key.",
|
||||
),
|
||||
setup_note=(
|
||||
"Search, Fetch and Research use prepaid USD credit. treg checks the free balance "
|
||||
"endpoint when you connect the key."
|
||||
),
|
||||
auth_uri="", token_uri="",
|
||||
scopes={},
|
||||
client_id_setting="", client_secret_setting="",
|
||||
category="SEO",
|
||||
summary="Search the web, fetch pages, and run sourced research.",
|
||||
base_url="https://api.linkup.so",
|
||||
docs_url="https://docs.linkup.so/pages/documentation/get-started/introduction",
|
||||
probe_path="/v1/credits/balance",
|
||||
)
|
||||
|
||||
SERPER = OAuthProvider(
|
||||
service="serper",
|
||||
display_name="Serper",
|
||||
@@ -3604,7 +3630,7 @@ REGISTRY: dict[str, OAuthProvider] = {
|
||||
TIKHUB, BRIGHTDATA, SEMRUSH, JUSTONEAPI,
|
||||
SCRAPECREATORS,
|
||||
# SEO API-key providers
|
||||
DATAFORSEO, SERANKING, MOZ, MAJESTIC, SERPSTAT, EXA, TAVILY, KEENABLE, OLOSTEP, FIRECRAWL,
|
||||
DATAFORSEO, SERANKING, MOZ, MAJESTIC, SERPSTAT, EXA, TAVILY, LINKUP, KEENABLE, OLOSTEP, FIRECRAWL,
|
||||
SCRAPEGRAPHAI, SERPER, CLORO,
|
||||
# more Enrichment API-key providers
|
||||
LUSHA, CORESIGNAL, DIFFBOT, THECOMPANIESAPI, LEADMAGIC, FIBER_AI, CRUSTDATA, AVIATO,
|
||||
|
||||
@@ -174,6 +174,7 @@ CATALOG: list[dict] = [
|
||||
{"provider": "Cartesia", "tokens": ["CARTESIA"], "base_url": "https://api.cartesia.ai", "auth": {"shape": "api_key_header", "header": "X-API-Key"}},
|
||||
# --- search / scraping ---
|
||||
{"provider": "Tavily", "tokens": ["TAVILY"], "base_url": "https://api.tavily.com", "auth": {"shape": "bearer"}},
|
||||
{"provider": "Linkup", "tokens": ["LINKUP"], "base_url": "https://api.linkup.so/v1", "auth": {"shape": "bearer"}, "probe": "credits/balance"},
|
||||
{"provider": "Keenable", "tokens": ["KEENABLE"], "base_url": "https://api.keenable.ai", "auth": {"shape": "api_key_header", "header": "X-API-Key"}},
|
||||
{"provider": "Olostep", "tokens": ["OLOSTEP"], "base_url": "https://api.olostep.com", "auth": {"shape": "bearer"}, "probe": "user/credits/info"},
|
||||
{"provider": "ScrapeGraphAI", "tokens": ["SCRAPEGRAPHAI", "SGAI"],
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg width="150" height="150" viewBox="0 0 150 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2273_1188)">
|
||||
<path d="M150 0H0V150H150V0Z" fill="white"/>
|
||||
<path d="M13.2505 117.037C10.8755 76.6623 25.1255 32.4873 53.6255 32.4873C79.7505 32.4873 94.0005 81.4123 99.2255 117.512" stroke="black" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.25 117.037C37 95.6621 65.5 67.1621 86.875 51.4871C101.125 42.4621 117.75 52.9121 114.9 71.9121C113.475 88.5371 107.3 102.787 99.225 117.512" stroke="black" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.25 117.037C37 106.587 79.75 95.6624 115.375 91.8624C127.25 90.4374 137.7 91.8624 136.75 98.5124C135.8 106.112 117.75 111.812 99.225 117.512" stroke="black" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2273_1188">
|
||||
<rect width="150" height="150" rx="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
@@ -438,6 +438,24 @@ async def test_firecrawl_balance_uses_free_credit_usage_endpoint():
|
||||
await collectors._firecrawl(client, "test-key")
|
||||
|
||||
|
||||
async def test_linkup_balance_uses_free_credit_route_and_rejects_invalid_values():
|
||||
def probe(request):
|
||||
assert str(request.url) == "https://api.linkup.so/v1/credits/balance"
|
||||
assert request.headers["authorization"] == "Bearer test-key"
|
||||
return httpx.Response(200, json={"balance": 12.34})
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(probe)) as client:
|
||||
assert await collectors._linkup(client, "test-key") == {
|
||||
"value": 12.34, "unit": "USD", "note": "prepaid credit balance",
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(
|
||||
lambda _: httpx.Response(200, json={"balance": True})
|
||||
)) as client:
|
||||
with pytest.raises(ValueError, match="valid USD balance"):
|
||||
await collectors._linkup(client, "test-key")
|
||||
|
||||
|
||||
async def test_scrapegraphai_balance(monkeypatch):
|
||||
monkeypatch.setenv("TREG_PLATFORM_KEY_SCRAPEGRAPHAI", "test-key")
|
||||
collectors.get_settings.cache_clear()
|
||||
|
||||
@@ -288,6 +288,7 @@ _UNRECORDED_SIGNATURE = {
|
||||
"keenable", # funded request balance remains; documented bare 402 was not forced
|
||||
"olostep", # funded credit balance remains; documented 402 was not forced
|
||||
"firecrawl", # credit balance remains; documented 402 was not deliberately forced
|
||||
"linkup", # 429 means either exhausted credit or excess concurrency; balance was not exhausted
|
||||
"scrapegraphai", # trial credits remain; no provider-specific empty-balance body was forced
|
||||
"scrubby", # funded account not exhausted; no provider-specific empty-balance body recorded
|
||||
"millionverifier", # funded-account exhaustion not observed; trial still has credits
|
||||
|
||||
@@ -132,6 +132,14 @@ def test_firecrawl_web_adapters_are_verified_routed_children():
|
||||
assert child in cat.by_id[parent]["routed_children"], (parent, child)
|
||||
|
||||
|
||||
def test_search_adapter_does_not_treat_an_answer_without_results_as_a_miss():
|
||||
adapter = catalog_store.load().adapters["linkup.web.search"]
|
||||
assert adapter.verified
|
||||
assert adapter.is_miss({"results": []})
|
||||
assert not adapter.is_miss({"answer": "A sourced answer", "sources": []})
|
||||
assert not adapter.is_miss({"data": {"answer": "A structured answer"}})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("parent", "child", "routed_input", "upstream_body", "expected_body", "expected_cost"), [
|
||||
(
|
||||
"treg.web.search", "firecrawl.web.search",
|
||||
|
||||
Reference in New Issue
Block a user