feat(catalog): add Linkup web and research tools

This commit is contained in:
shehjad-dev
2026-09-29 02:06:20 +06:00
parent b3005ec20e
commit 7b9aeb942f
25 changed files with 425 additions and 12 deletions
+8 -1
View File
@@ -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
+17
View File
@@ -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
+8
View File
@@ -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
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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.
+14
View File
@@ -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"
}
]
}
+219
View File
@@ -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
+1
View File
@@ -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
+11
View File
@@ -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,
+2
View File
@@ -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"},
+27 -1
View File
@@ -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,
+1
View File
@@ -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"],
+13
View File
@@ -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

+18
View File
@@ -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()
+1
View File
@@ -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
+8
View File
@@ -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",