Merge pull request #542 from superdesigndev/codex/provider-openmart

feat(catalog): add Openmart provider
This commit is contained in:
Taus
2026-09-17 10:16:49 +06:00
committed by GitHub
46 changed files with 987 additions and 37 deletions
+38 -19
View File
@@ -100,10 +100,10 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/application/call/invite.py` | architecture/feedback.md |
| `src/treg/application/call/overflow.py` | architecture/import-boundaries.md, ops/capacity.md |
| `src/treg/application/call/reserve.py` | architecture/import-boundaries.md, architecture/money.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/application/call/resolve.py` | architecture/contactout.md, architecture/dropleads.md, architecture/harvestapi.md, architecture/import-boundaries.md, architecture/instagram-oauth.md, architecture/money.md, architecture/multi-tenancy.md, architecture/prospeo.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/application/call/resolve.py` | architecture/contactout.md, architecture/dropleads.md, architecture/harvestapi.md, architecture/import-boundaries.md, architecture/instagram-oauth.md, architecture/money.md, architecture/multi-tenancy.md, architecture/openmart.md, architecture/prospeo.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/application/call/route.py` | architecture/catalog.md, architecture/import-boundaries.md |
| `src/treg/application/call/service.py` | architecture/archive.md, architecture/import-boundaries.md, architecture/instagram-oauth.md, architecture/money.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/application/call/settle.py` | architecture/aiark.md, architecture/archive.md, architecture/dropleads.md, architecture/import-boundaries.md, architecture/money.md, architecture/prospeo.md, architecture/proxy-model.md, architecture/scrubby.md, interface/api.md |
| `src/treg/application/call/settle.py` | architecture/aiark.md, architecture/archive.md, architecture/dropleads.md, architecture/import-boundaries.md, architecture/money.md, architecture/openmart.md, architecture/prospeo.md, architecture/proxy-model.md, architecture/scrubby.md, interface/api.md |
| `src/treg/application/call/sumble.py` | architecture/sumble.md |
| `src/treg/application/call/types.py` | architecture/import-boundaries.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/application/catalog_stats.py` | architecture/catalog.md |
@@ -125,7 +125,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/bootstrap_http.py` | architecture/composition.md, interface/api.md |
| `src/treg/call_surface.py` | architecture/composition.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/caller_metadata.py` | architecture/multi-tenancy.md, interface/api.md |
| `src/treg/catalog/adapters.yaml` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/contactout.md, architecture/dropleads.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `src/treg/catalog/adapters.yaml` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/contactout.md, architecture/dropleads.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `src/treg/catalog/aiark.yaml` | architecture/aiark.md |
| `src/treg/catalog/akta.extended.yaml` | architecture/catalog.md |
| `src/treg/catalog/aliases.yaml` | architecture/catalog.md |
@@ -303,6 +303,22 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/catalog/examples/moltsets.people.enrich.name.json` | architecture/moltsets.md |
| `src/treg/catalog/examples/moltsets.people.phone.find.json` | architecture/moltsets.md |
| `src/treg/catalog/examples/moltsets.people.search.json` | architecture/moltsets.md |
| `src/treg/catalog/examples/openmart.businesses.lookup.google-place.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.businesses.lookup.openmart.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.businesses.search.ids.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.businesses.search.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.companies.email.find.batch.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.companies.enrich.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.companies.search.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.deny-rules.check.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.deny-rules.create.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.deny-rules.delete.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.people.enrich.batch.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.people.find.batch.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.tasks.batch.ids.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.tasks.batch.status.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.tasks.get.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openmart.technologies.find.batch.json` | architecture/openmart.md |
| `src/treg/catalog/examples/openrouter.video-gen.seedance-2-5.json` | architecture/catalog.md |
| `src/treg/catalog/examples/openrouter.x.alibaba-wan-3-0.json` | architecture/catalog.md |
| `src/treg/catalog/examples/piapi.image-gen.gemini-3-pro-image.json` | architecture/catalog.md |
@@ -360,7 +376,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/catalog/examples/zerobounce.people.email.find.json` | architecture/zerobounce.md |
| `src/treg/catalog/examples/zerobounce.people.email.verify.json` | architecture/zerobounce.md |
| `src/treg/catalog/financialdatasets.yaml` | architecture/catalog.md |
| `src/treg/catalog/fx.yaml` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `src/treg/catalog/fx.yaml` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `src/treg/catalog/getleadsio.yaml` | architecture/getleadsio.md |
| `src/treg/catalog/google-search-console.extended.yaml` | architecture/catalog.md |
| `src/treg/catalog/google-search-console.yaml` | architecture/catalog.md |
@@ -375,6 +391,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/catalog/minimax.yaml` | architecture/catalog.md |
| `src/treg/catalog/moltsets.yaml` | architecture/moltsets.md |
| `src/treg/catalog/oceanio.yaml` | architecture/catalog.md |
| `src/treg/catalog/openmart.yaml` | architecture/openmart.md |
| `src/treg/catalog/openrouter.extended.yaml` | architecture/catalog.md |
| `src/treg/catalog/openrouter.yaml` | architecture/catalog.md |
| `src/treg/catalog/piapi.yaml` | architecture/catalog.md |
@@ -397,7 +414,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/cli.py` | architecture/instagram-oauth.md, interface/cli.md, interface/onboarding.md, interface/shell.md |
| `src/treg/cli_analytics.py` | interface/cli.md |
| `src/treg/client_identity.py` | architecture/import-boundaries.md, architecture/proxy-model.md, interface/api.md |
| `src/treg/config.py` | architecture/aiark.md, architecture/archive.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/dropleads.md, architecture/feedback.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/super-admin.md, architecture/wiza.md, architecture/zerobounce.md, guides/expanding-a-category.md, ops/deploy.md |
| `src/treg/config.py` | architecture/aiark.md, architecture/archive.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/dropleads.md, architecture/feedback.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/super-admin.md, architecture/wiza.md, architecture/zerobounce.md, guides/expanding-a-category.md, ops/deploy.md |
| `src/treg/convert.py` | interface/cli.md |
| `src/treg/crypto.py` | architecture/auth-secrets.md |
| `src/treg/domain/__init__.py` | architecture/import-boundaries.md |
@@ -405,11 +422,11 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/domain/arena_insights.py` | interface/enrich-arena.md |
| `src/treg/domain/asynctasks/__init__.py` | architecture/import-boundaries.md, architecture/money.md |
| `src/treg/domain/capacity/__init__.py` | architecture/import-boundaries.md, ops/capacity.md |
| `src/treg/domain/capacity/collectors.py` | architecture/aiark.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `src/treg/domain/capacity/collectors.py` | architecture/aiark.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `src/treg/domain/capacity/marks.py` | ops/capacity.md |
| `src/treg/domain/capacity/overflow_seed.json` | ops/capacity.md |
| `src/treg/domain/capacity/overflow_spend.py` | ops/capacity.md |
| `src/treg/domain/capacity/policy.py` | architecture/aiark.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `src/treg/domain/capacity/policy.py` | architecture/aiark.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `src/treg/domain/capacity/routes.py` | ops/capacity.md |
| `src/treg/domain/capacity/routes_view.py` | ops/capacity.md |
| `src/treg/domain/capacity/signatures.py` | ops/capacity.md |
@@ -424,7 +441,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/domain/catalog/routing/plan.py` | architecture/catalog.md |
| `src/treg/domain/catalog/routing/synthetic.py` | architecture/catalog.md |
| `src/treg/domain/catalog/stats.py` | architecture/catalog.md |
| `src/treg/domain/catalog/store.py` | architecture/catalog.md, architecture/harvestapi.md, architecture/instagram-oauth.md, interface/api.md, interface/catalog-review-proposal.md |
| `src/treg/domain/catalog/store.py` | architecture/catalog.md, architecture/harvestapi.md, architecture/instagram-oauth.md, architecture/openmart.md, interface/api.md, interface/catalog-review-proposal.md |
| `src/treg/domain/connections/__init__.py` | architecture/auth-secrets.md, architecture/import-boundaries.md |
| `src/treg/domain/connections/authorization.py` | architecture/auth-secrets.md, architecture/import-boundaries.md, architecture/instagram-oauth.md, guides/expanding-a-category.md |
| `src/treg/domain/connections/oauth_flow.py` | architecture/auth-secrets.md, architecture/import-boundaries.md, architecture/instagram-oauth.md, guides/expanding-a-category.md |
@@ -482,8 +499,8 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/mcp_install.py` | interface/skill.md |
| `src/treg/models.py` | architecture/data-model.md, architecture/media.md, architecture/money.md, architecture/multi-tenancy.md, interface/enrich-arena.md |
| `src/treg/oauth.py` | architecture/auth-secrets.md |
| `src/treg/oauth_providers.py` | architecture/aiark.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, guides/expanding-a-category.md |
| `src/treg/providers.py` | architecture/moltsets.md, interface/env-import.md |
| `src/treg/oauth_providers.py` | architecture/aiark.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/harvestapi.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, guides/expanding-a-category.md |
| `src/treg/providers.py` | architecture/moltsets.md, architecture/openmart.md, interface/env-import.md |
| `src/treg/ratestore.py` | architecture/data-model.md, interface/api.md |
| `src/treg/reconcile.py` | architecture/money.md |
| `src/treg/routers/__init__.py` | interface/api.md |
@@ -544,6 +561,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/web/logos/leadsforge.svg` | interface/enrich-arena.md |
| `src/treg/web/logos/lusha.svg` | interface/enrich-arena.md |
| `src/treg/web/logos/moltsets.svg` | architecture/moltsets.md |
| `src/treg/web/logos/openmart.svg` | architecture/openmart.md |
| `src/treg/web/logos/pdl.svg` | interface/enrich-arena.md |
| `src/treg/web/logos/predictleads.svg` | interface/enrich-arena.md |
| `src/treg/web/logos/prospeo.svg` | architecture/prospeo.md |
@@ -577,7 +595,7 @@ Regenerate via `scripts/build-map.py`.
| `src/treg/web/vendor/README.md` | interface/dashboard.md |
| `src/treg/web/vendor/vue-3.5.41.global.prod.js` | interface/dashboard.md |
| `src/treg/worker.py` | ops/capacity.md, ops/deploy.md |
| `tests/conftest.py` | architecture/aiark.md, architecture/moltsets.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/conftest.py` | architecture/aiark.md, architecture/moltsets.md, architecture/openmart.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/fake_object_store.py` | architecture/archive.md |
| `tests/fixtures/aggregators/verification/contactout.json` | architecture/contactout.md |
| `tests/js/arena-bench.test.cjs` | interface/enrich-arena.md |
@@ -598,14 +616,14 @@ Regenerate via `scripts/build-map.py`.
| `tests/test_call_architecture.py` | architecture/import-boundaries.md, architecture/money.md, architecture/proxy-model.md |
| `tests/test_call_cancellation.py` | architecture/proxy-model.md |
| `tests/test_call_response_limits.py` | architecture/proxy-model.md |
| `tests/test_capacity_collectors.py` | architecture/aiark.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `tests/test_capacity_collectors.py` | architecture/aiark.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `tests/test_capacity_know.py` | ops/capacity.md |
| `tests/test_capacity_overflow.py` | architecture/contactout.md, ops/capacity.md |
| `tests/test_capacity_overflow_routes.py` | architecture/aiark.md, architecture/bounceban.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `tests/test_capacity_overflow_routes.py` | architecture/aiark.md, architecture/bounceban.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/openmart.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md, ops/capacity.md |
| `tests/test_capacity_overflow_spend.py` | ops/capacity.md |
| `tests/test_capacity_protect.py` | ops/capacity.md |
| `tests/test_capacity_smoothing.py` | ops/capacity.md |
| `tests/test_catalog_api.py` | architecture/catalog.md, architecture/getleadsio.md |
| `tests/test_catalog_api.py` | architecture/catalog.md, architecture/getleadsio.md, architecture/openmart.md |
| `tests/test_catalog_validate.py` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/prospeo.md, architecture/zerobounce.md |
| `tests/test_cli_key_compatibility.py` | interface/cli.md |
| `tests/test_dataforseo_constraints.py` | architecture/catalog.md |
@@ -619,16 +637,16 @@ Regenerate via `scripts/build-map.py`.
| `tests/test_import_lightness.py` | architecture/import-boundaries.md |
| `tests/test_influencersclub_overflow.py` | ops/capacity.md |
| `tests/test_instagram_oauth_architecture.py` | architecture/instagram-oauth.md |
| `tests/test_key_providers.py` | architecture/aiark.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_key_providers.py` | architecture/aiark.md, architecture/auth-secrets.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/openmart.md, architecture/prospeo.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_kv.py` | architecture/feedback.md |
| `tests/test_marketplace_call.py` | architecture/aiark.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/mcp-oauth.md, architecture/prospeo.md, architecture/proxy-model.md, architecture/zerobounce.md |
| `tests/test_mcp.py` | architecture/mcp-oauth.md |
| `tests/test_marketplace_call.py` | architecture/aiark.md, architecture/bounceban.md, architecture/contactout.md, architecture/dropleads.md, architecture/getleadsio.md, architecture/mcp-oauth.md, architecture/openmart.md, architecture/prospeo.md, architecture/proxy-model.md, architecture/zerobounce.md |
| `tests/test_mcp.py` | architecture/mcp-oauth.md, architecture/openmart.md |
| `tests/test_mcp_directory.py` | architecture/mcp-oauth.md |
| `tests/test_mcp_oauth.py` | architecture/mcp-oauth.md |
| `tests/test_media.py` | architecture/media.md |
| `tests/test_moltsets.py` | architecture/moltsets.md |
| `tests/test_oauth_billed.py` | architecture/proxy-model.md |
| `tests/test_oauth_providers_m3.py` | architecture/aiark.md, architecture/bounceban.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_oauth_providers_m3.py` | architecture/aiark.md, architecture/bounceban.md, architecture/getleadsio.md, architecture/moltsets.md, architecture/openmart.md, architecture/scrubby.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_oauth_refresh.py` | architecture/auth-secrets.md |
| `tests/test_passthrough.py` | architecture/proxy-model.md |
| `tests/test_postgres_reset.py` | architecture/data-model.md |
@@ -637,7 +655,7 @@ Regenerate via `scripts/build-map.py`.
| `tests/test_reviews.py` | architecture/feedback.md |
| `tests/test_route_cost_ceiling.py` | architecture/catalog.md |
| `tests/test_router_dependencies.py` | architecture/multi-tenancy.md |
| `tests/test_routing.py` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/contactout.md, architecture/dropleads.md, architecture/prospeo.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_routing.py` | architecture/aiark.md, architecture/bounceban.md, architecture/catalog.md, architecture/contactout.md, architecture/dropleads.md, architecture/openmart.md, architecture/prospeo.md, architecture/wiza.md, architecture/zerobounce.md |
| `tests/test_scrubby.py` | architecture/scrubby.md |
| `tests/test_ssrf_public_addresses.py` | architecture/proxy-model.md |
| `tests/test_sumble.py` | architecture/sumble.md |
@@ -673,6 +691,7 @@ Regenerate via `scripts/build-map.py`.
| `architecture/moltsets.md` | `moltsets.yaml`, `moltsets.people.search.json`, `moltsets.companies.search.json`, `moltsets.linkedin.profile.search.json`, `moltsets.people.email.find.name.json`, `moltsets.people.enrich.name.json`, `moltsets.people.email.find.json`, `moltsets.people.email.find.business.json`, `moltsets.people.email.find.personal.json`, `moltsets.people.email.find.personal-best.json`, `moltsets.people.phone.find.json`, `moltsets.people.enrich.email.json`, `moltsets.people.enrich.linkedin.json`, `moltsets.people.audiences.maid.json`, `moltsets.people.audiences.sha256.json`, `moltsets.people.audiences.hashes.json`, `moltsets.linkedin.profile.from-email.json`, `moltsets.companies.identify.ip.json`, `adapters.yaml`, `fx.yaml`, `oauth_providers.py`, `providers.py`, `config.py`, `collectors.py`, `policy.py`, `moltsets.svg`, `test_moltsets.py`, `test_capacity_overflow_routes.py`, `conftest.py`, `test_key_providers.py`, `test_oauth_providers_m3.py`, `test_providers.py` |
| `architecture/money.md` | `__init__.py`, `settlement.py`, `__init__.py`, `models.py`, `billing.py`, `idempotency.py`, `intake.py`, `resolve.py`, `service.py`, `reserve.py`, `settle.py`, `tomba.yaml`, `asynctasks.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `0019_async_poll_failures.py`, `referrals.py`, `budgets.py`, `__init__.py`, `stripe.py`, `reconcile.py`, `referrals.py`, `api.py`, `signup.py`, `promotions.py`, `0033_signup_promo_eligibility.py`, `admin.py`, `billing.py`, `call.py`, `orgs.py`, `referrals.py`, `test_call_architecture.py`, `test_asynctasks.py` |
| `architecture/multi-tenancy.md` | `models.py`, `api.py`, `caller_metadata.py`, `auth.py`, `asynctasks.py`, `resolve.py`, `signup.py`, `access.py`, `budgets.py`, `publicdemo.py`, `teams.py`, `usage.py`, `access.py`, `api_keys.py`, `session.py`, `promotions.py`, `test_team_limit.py`, `test_auth.py`, `test_token_revocation.py`, `auth.py`, `orgs.py`, `resources.py`, `bundles.py`, `db.py`, `0017_async_task_record.py`, `0018_async_resource_ownership.py`, `test_router_dependencies.py`, `test_asynctasks.py` |
| `architecture/openmart.md` | `openmart.yaml`, `openmart.businesses.search.json`, `openmart.businesses.search.ids.json`, `openmart.businesses.lookup.openmart.json`, `openmart.businesses.lookup.google-place.json`, `openmart.companies.enrich.json`, `openmart.companies.search.json`, `openmart.people.find.batch.json`, `openmart.technologies.find.batch.json`, `openmart.companies.email.find.batch.json`, `openmart.people.enrich.batch.json`, `openmart.tasks.batch.status.json`, `openmart.tasks.batch.ids.json`, `openmart.tasks.get.json`, `openmart.deny-rules.create.json`, `openmart.deny-rules.check.json`, `openmart.deny-rules.delete.json`, `adapters.yaml`, `fx.yaml`, `oauth_providers.py`, `providers.py`, `config.py`, `resolve.py`, `settle.py`, `store.py`, `collectors.py`, `policy.py`, `openmart.svg`, `test_capacity_overflow_routes.py`, `test_capacity_collectors.py`, `test_catalog_api.py`, `conftest.py`, `test_key_providers.py`, `test_marketplace_call.py`, `test_mcp.py`, `test_oauth_providers_m3.py`, `test_routing.py` |
| `architecture/prospeo.md` | `prospeo.yaml`, `prospeo.people.email.find.json`, `prospeo.people.phone.find.json`, `prospeo.people.enrich.json`, `prospeo.people.enrich.bulk.json`, `prospeo.people.search.json`, `prospeo.companies.enrich.json`, `prospeo.companies.enrich.bulk.json`, `prospeo.companies.search.json`, `prospeo.search.suggestions.json`, `adapters.yaml`, `fx.yaml`, `config.py`, `oauth_providers.py`, `resolve.py`, `settle.py`, `collectors.py`, `policy.py`, `prospeo.svg`, `provider_balances.py`, `test_catalog_validate.py`, `test_key_providers.py`, `test_marketplace_call.py`, `test_routing.py`, `test_capacity_collectors.py` |
| `architecture/proxy-model.md` | `relay.py`, `ssrf.py`, `api.py`, `authorize.py`, `idempotency.py`, `intake.py`, `resolve.py`, `reserve.py`, `settle.py`, `evidence.py`, `service.py`, `types.py`, `asynctasks.py`, `client_identity.py`, `call_surface.py`, `sandbox_identity.py`, `access.py`, `publicdemo.py`, `usage.py`, `call.py`, `test_ssrf_public_addresses.py`, `test_call_application_contract.py`, `test_call_cancellation.py`, `test_call_response_limits.py`, `test_error_capture.py`, `test_marketplace_call.py`, `test_oauth_billed.py`, `test_passthrough.py`, `test_tag_billing.py`, `test_tag_billing_adversarial.py`, `test_call_architecture.py`, `test_asynctasks.py` |
| `architecture/scrubby.md` | `scrubby.yaml`, `scrubby.people.email.verify.json`, `scrubby.people.email.verify.bulk.json`, `scrubby.people.email.verify.bulk.results.json`, `scrubby.people.email.verify.deep.json`, `scrubby.people.email.verify.deep.results.json`, `adapters.yaml`, `fx.yaml`, `config.py`, `oauth_providers.py`, `settle.py`, `collectors.py`, `policy.py`, `scrubby.svg`, `test_scrubby.py`, `test_key_providers.py`, `test_oauth_providers_m3.py`, `test_capacity_overflow_routes.py`, `test_capacity_collectors.py`, `conftest.py` |
+1
View File
@@ -38,6 +38,7 @@ covers (frontmatter `sources:`). Regenerate this index with
| [MoltSets — enrichment records, shared-plan pricing and BYOK](architecture/moltsets.md) | shipped | moltsets.yaml, moltsets.people.search.json, moltsets.companies.search.json, moltsets.linkedin.profile.search.json, … |
| [Money — prepaid balance, the ledger, Stripe, and the reports that check it](architecture/money.md) | shipped | __init__.py, settlement.py, __init__.py, models.py, … |
| [Multi-tenancy — orgs, memberships, invites, per-org scoping](architecture/multi-tenancy.md) | shipped | models.py, api.py, caller_metadata.py, auth.py, … |
| [Openmart — metered synchronous data and BYOK lifecycle boundaries](architecture/openmart.md) | shipped | openmart.yaml, openmart.businesses.search.json, openmart.businesses.search.ids.json, openmart.businesses.lookup.openmart.json, … |
| [Prospeo — people and company enrichment](architecture/prospeo.md) | shipped | prospeo.yaml, prospeo.people.email.find.json, prospeo.people.phone.find.json, prospeo.people.enrich.json, … |
| [The proxy — faithful credential-injecting relay + tool resolution](architecture/proxy-model.md) | shipped | relay.py, ssrf.py, api.py, authorize.py, … |
| [Scrubby — quick and deep email verification](architecture/scrubby.md) | implemented; live authentication, billing and asynchronous behavior verified | scrubby.yaml, scrubby.people.email.verify.json, scrubby.people.email.verify.bulk.json, scrubby.people.email.verify.bulk.results.json, … |
-1
View File
@@ -506,7 +506,6 @@ settled 20 rows, moz's one `targets` entry settled 20 quota rows; 2026-09-02: lu
catalogued FREE, answered 44 contacts for one domain and settled $5.49 from `billing.creditsCharged`
with nothing reserved). Without any signal it is the
20-row page, and a settle-at-estimate provider then charges that page.
The page default has no meaning at all when the catalog prices per INPUT entity, and the estimator
knows the difference since 2026-09-05: a `per_result`/`quota_rows` cost whose `unit` is `target`,
`domain`, `keyword` or `call` (`resolve._ENTITY_UNITS`) is counted by `_entity_count` — repeated or
+106
View File
@@ -0,0 +1,106 @@
---
title: Openmart — metered synchronous data and BYOK lifecycle boundaries
status: shipped
sources:
- src/treg/catalog/openmart.yaml
- src/treg/catalog/examples/openmart.businesses.search.json
- src/treg/catalog/examples/openmart.businesses.search.ids.json
- src/treg/catalog/examples/openmart.businesses.lookup.openmart.json
- src/treg/catalog/examples/openmart.businesses.lookup.google-place.json
- src/treg/catalog/examples/openmart.companies.enrich.json
- src/treg/catalog/examples/openmart.companies.search.json
- src/treg/catalog/examples/openmart.people.find.batch.json
- src/treg/catalog/examples/openmart.technologies.find.batch.json
- src/treg/catalog/examples/openmart.companies.email.find.batch.json
- src/treg/catalog/examples/openmart.people.enrich.batch.json
- src/treg/catalog/examples/openmart.tasks.batch.status.json
- src/treg/catalog/examples/openmart.tasks.batch.ids.json
- src/treg/catalog/examples/openmart.tasks.get.json
- src/treg/catalog/examples/openmart.deny-rules.create.json
- src/treg/catalog/examples/openmart.deny-rules.check.json
- src/treg/catalog/examples/openmart.deny-rules.delete.json
- src/treg/catalog/adapters.yaml
- src/treg/catalog/fx.yaml
- src/treg/oauth_providers.py
- src/treg/providers.py
- src/treg/config.py
- src/treg/application/call/resolve.py
- src/treg/application/call/settle.py
- src/treg/domain/catalog/store.py
- src/treg/domain/capacity/collectors.py
- src/treg/domain/capacity/policy.py
- src/treg/web/logos/openmart.svg
- tests/test_capacity_overflow_routes.py
- tests/test_capacity_collectors.py
- tests/test_catalog_api.py
- tests/conftest.py
- tests/test_key_providers.py
- tests/test_marketplace_call.py
- tests/test_mcp.py
- tests/test_oauth_providers_m3.py
- tests/test_routing.py
related:
- architecture/catalog.md
- architecture/auth-secrets.md
- architecture/money.md
- interface/enrich-arena.md
- ops/capacity.md
---
# Openmart
Openmart is a pasted Bearer-key enrichment provider at `https://api.openmart.ai`. The free
`GET /api/v2/credit-balance` operation verifies a connection and supplies the capacity collector.
The catalog exposes 16 caller-facing operations: business and brand search, two ID lookups, company
enrichment, four batch submission types, three task reads, and three deny-rule operations. The
balance route is deliberately not a tool: it is internal connection/capacity infrastructure.
## Shared-key boundary
Five synchronous data operations support both a team's own key and treg's metered platform key:
business search, both full-record ID lookups, company enrichment, and brand search. The team's key
still wins and is never metered. Openmart's provider-specific settlement path counts only the
verified returned-record containers: a root array or `data[]` for search/enrichment, `data[]` for
brand search, and values in the ID-keyed lookup maps. It charges
`ceil(0.3 × returned_records)` credits per operation; a present empty container settles at zero.
Shared-key requests require an explicit count from 1 to 25 before reserve or relay: `limit` for
business search and company enrichment, `pagination.limit` for brand search, and raw array
cardinality for both ID lookups. The reserve applies the same rounded formula, so 25 requested
records hold eight credits. Own-key calls bypass the guard and retain the upstream limits.
The other 11 operations remain BYOK-only. Fast ID search has no proven fractional price. The four
batch submissions and three task reads are one delayed, account-owned lifecycle whose charges
cannot be assigned safely by a synchronous response. The three deny-rule operations read or mutate
private account state and forbid caching. The shared balance route is absent from the catalog so no
caller can inspect operational inventory.
The catalog records the active subscription conversion of $149 for 5,000 credits, or $0.0298 per
credit, before configured platform margin. Openmart support confirmed that search, full-record
lookup, and company enrichment are priced at three credits per ten returned records, rounded up per
operation. Ten live one-result searches each returned one record and deducted one credit, matching
that rule. A 25-record platform call therefore reserves at most eight credits ($0.2384 raw). People
data costs 3 credits for email and 8 for phone; a technology result costs 2. The catalog does not
claim auto-top-up.
## Routing and Arena
Only `openmart.companies.search` has an adapter. It maps a canonical text or name, optional country,
and limit to brand search. Its stored fixture verifies companies, count, and cursor output. The
child is platform-eligible and participates in the generated `treg.companies.search` tool.
Company enrichment can return several location matches. Choosing the first would change semantics,
so it does not join `companies.enrich`. People operations are asynchronous and do not join the
synchronous people routes or Arena tasks. No new routed contract or Arena branch is added.
## Capacity and evidence
`collectors._openmart` calls the free balance endpoint, accepts only a non-negative integer balance,
and reports subscription credits with the current period end when available. The default policy is
`credits / subscription / api`. A provider-wide 15 requests per second policy uses the strictest
published endpoint-family limit. Direct BYOK calls bypass shared-key smoothing.
Live checks used synthetic inputs and stayed within the task cap. They covered authentication,
search pagination, hits, misses, validation, every data operation, the full batch lifecycle, deny
checks, and internal balance reads. Stored examples use reserved synthetic identities only. No key
or raw live response is stored.
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: treg
description: Reach for this first for external or live data. 3,400+ endpoints across 83 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,400+ endpoints across 84 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,400+ catalogued endpoints across 83 providers, grouped by what they DO: keyword & rank tracking,
3,400+ catalogued endpoints across 84 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,400+ endpoints across 83 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,400+ endpoints across 84 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,400+ catalogued endpoints across 83 providers, grouped by what they DO: keyword & rank tracking,
3,400+ catalogued endpoints across 84 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,400+ endpoints across 83 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,400+ endpoints across 84 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.19.1
---
@@ -84,7 +84,7 @@ spends nothing: that key belongs to them.
## Task — the catalog: what treg can do for you (start here)
3,400+ catalogued endpoints across 83 providers, grouped by what they DO: keyword & rank tracking,
3,400+ catalogued endpoints across 84 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,400+ endpoints across 83 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,400+ endpoints across 84 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.19.1
---
@@ -98,7 +98,7 @@ spends nothing: that key belongs to them.
## Task — the catalog: what treg can do for you (start here)
3,400+ catalogued endpoints across 83 providers, grouped by what they DO: keyword & rank tracking,
3,400+ catalogued endpoints across 84 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,400+ endpoints across 83 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,400+ endpoints across 84 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.19.1
---
@@ -82,7 +82,7 @@ spends nothing: that key belongs to them.
## Task — the catalog: what treg can do for you (start here)
3,400+ catalogued endpoints across 83 providers, grouped by what they DO: keyword & rank tracking,
3,400+ catalogued endpoints across 84 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.
+67
View File
@@ -515,6 +515,45 @@ def _truthy(value) -> bool:
return value is True or (isinstance(value, str) and value.strip().lower() in ("1", "true", "yes"))
_OPENMART_METERED_ENDPOINTS = frozenset({
"openmart.businesses.search",
"openmart.businesses.lookup.openmart",
"openmart.businesses.lookup.google-place",
"openmart.companies.enrich",
"openmart.companies.search",
})
_OPENMART_LOOKUP_ENDPOINTS = frozenset({
"openmart.businesses.lookup.openmart",
"openmart.businesses.lookup.google-place",
})
_OPENMART_PLATFORM_MAX_RECORDS = 25
def _openmart_credits(records: int) -> int:
"""Openmart bills 3 credits per 10 returned records, rounded up per operation."""
return 0 if records <= 0 else (3 * records + 9) // 10
def _openmart_requested_records(endpoint_id: str, body: bytes) -> int | None:
"""Read the requested Openmart result ceiling without changing a BYOK request."""
if not body:
return None
try:
document = json.loads(body)
except (ValueError, UnicodeDecodeError):
return None
if endpoint_id in _OPENMART_LOOKUP_ENDPOINTS:
return len(document) if isinstance(document, list) else None
if not isinstance(document, dict):
return None
if endpoint_id == "openmart.companies.search":
pagination = document.get("pagination")
value = pagination.get("limit") if isinstance(pagination, dict) else None
else:
value = document.get("limit")
return value if type(value) is int else None
def _json_object(body: bytes) -> dict:
try:
doc = json.loads(body) if body else {}
@@ -580,6 +619,14 @@ def _marketplace_pricing(
"""
if not cost:
return 0, 0
if provider == "openmart" and endpoint_id in _OPENMART_METERED_ENDPOINTS:
rate = catalog_store.load().credit_rates.get("openmart")
if rate:
requested = _openmart_requested_records(endpoint_id, body)
bounded = max(1, min(requested or _OPENMART_PLATFORM_MAX_RECORDS,
_OPENMART_PLATFORM_MAX_RECORDS))
credit_micro = _usd_to_micro(rate)
return _openmart_credits(bounded) * credit_micro, credit_micro
if provider == "sumble" and cost.get("sumble"):
from . import sumble
credit = _usd_to_micro(float(cost.get("usd") or 0) * int(cost.get("per") or 1))
@@ -1099,6 +1146,26 @@ def _enforce_platform_request(ep: dict, body: bytes) -> None:
has a singleton enum is the row identity, not caller choice: accepting another value lets a cheap
row reserve for an expensive model. Full schema validation remains out of the faithful BYOK path.
"""
if ep.get("provider") == "openmart" and ep.get("id") in _OPENMART_METERED_ENDPOINTS:
requested = _openmart_requested_records(ep["id"], body)
parameter = (
"body" if ep["id"] in _OPENMART_LOOKUP_ENDPOINTS
else "body.pagination.limit" if ep["id"] == "openmart.companies.search"
else "body.limit"
)
if requested is None or not 1 <= requested <= _OPENMART_PLATFORM_MAX_RECORDS:
raise ResolutionFailed(
"catalog_parameter_invalid", status_code=400, detail={
"error": "catalog_parameter_invalid",
"endpoint_id": ep["id"],
"parameter": parameter,
"message": (
"Openmart platform calls require an explicit result count from 1 to 25; "
"connect your own key for the upstream limit"
),
},
)
input_schema = ep.get("input") or {}
selectors: dict[str, object] = {
path.removeprefix("body."): value
+27 -1
View File
@@ -26,7 +26,7 @@ from ...infra.db import session_maker
from ...models import Org
from ...timeutil import utcnow_naive as _utcnow_naive
from .idempotency import _release_idempotent_claim
from .resolve import MarketplaceCall, _usd_to_micro
from .resolve import MarketplaceCall, _openmart_credits, _usd_to_micro
from .types import GatewayFailed, UpstreamResponse
@@ -111,6 +111,29 @@ def _brightdata_record_count(body: bytes) -> int | None:
return None
def _openmart_record_count(endpoint_id: str, doc: object) -> int | None:
"""Count only the response containers verified for Openmart's synchronous data reads."""
if endpoint_id in (
"openmart.businesses.search",
"openmart.companies.enrich",
):
if isinstance(doc, list):
return sum(item is not None for item in doc)
if isinstance(doc, dict) and isinstance(doc.get("data"), list):
return sum(item is not None for item in doc["data"])
return None
if endpoint_id == "openmart.companies.search":
if isinstance(doc, dict) and isinstance(doc.get("data"), list):
return sum(item is not None for item in doc["data"])
return None
if endpoint_id in (
"openmart.businesses.lookup.openmart",
"openmart.businesses.lookup.google-place",
):
return sum(item is not None for item in doc.values()) if isinstance(doc, dict) else None
return None
def _quickenrich_cost_micro(mk: MarketplaceCall, doc: dict) -> int | None:
"""Subscription credits at the frozen list rate, independent of the upstream plan fee."""
if mk.cost_type == "free":
@@ -300,6 +323,9 @@ def _observed_cost_micro(mk: MarketplaceCall, body: bytes, headers=None) -> int
doc = json.loads(body)
except (ValueError, UnicodeDecodeError):
return 0 if provider == "contactout" else None
if provider == "openmart" and mk.cost_type == "per_result" and mk.unit_micro > 0:
records = _openmart_record_count(mk.endpoint_id, doc)
return None if records is None else _openmart_credits(records) * mk.unit_micro
if provider == "aviato" and mk.endpoint_id == "aviato.people.enrich.bulk":
if isinstance(doc, list) and mk.unit_micro > 0:
return sum(item is not None for item in doc) * mk.unit_micro
+10
View File
@@ -976,6 +976,16 @@ adapters:
const: {body.category: company}
out: {companies: results, count: "len(results)"}
miss: "results == []"
openmart.companies.search:
accepts: [[q], [name]]
in: {}
in_expr:
body.search_param.search_term: "coalesce(q, name)"
body.search_param.location: "choose(country, list(obj('country', upper(country))), null)"
body.pagination.limit: limit
test_identity: {q: coffee shop, country: US}
out: {companies: data, count: "len(data)", next_cursor: encoded_cursor}
miss: "data == []"
hunter.x.discover-companies:
accepts: [[q]]
in: {q: body.query}
@@ -0,0 +1 @@
{"example_place_id":{"openmart_id":"00000000-0000-4000-8000-000000000001","name":"Example Coffee","website":"https://example.com"}}
@@ -0,0 +1 @@
{"00000000-0000-4000-8000-000000000001":{"openmart_id":"00000000-0000-4000-8000-000000000001","name":"Example Coffee","website":"https://example.com"}}
@@ -0,0 +1 @@
{"data":["00000000-0000-4000-8000-000000000001"],"cursor":[0.98,"00000000-0000-4000-8000-000000000001"]}
@@ -0,0 +1 @@
{"data":[{"openmart_id":"00000000-0000-4000-8000-000000000001","name":"Example Coffee","website":"https://example.com","country":"US"}],"cursor":[0.98,"00000000-0000-4000-8000-000000000001"]}
@@ -0,0 +1 @@
{"batch_id":"00000000-0000-4000-8000-000000000030","task_ids":["00000000-0000-4000-8000-000000000031"]}
@@ -0,0 +1 @@
[{"input":"example.com","matches":[{"openmart_id":"00000000-0000-4000-8000-000000000001","name":"Example Coffee","website":"https://example.com"}]}]
@@ -0,0 +1 @@
{"data":[{"openmart_id":"00000000-0000-4000-8000-000000000001","name":"Example Coffee","website":"https://example.com","country":"US"}],"encoded_cursor":"ZXhhbXBsZS1jdXJzb3I="}
@@ -0,0 +1 @@
{"results":[{"type":"DOMAIN","value":"example.invalid","denied":false}]}
@@ -0,0 +1 @@
{"created":1,"rules":[{"type":"DOMAIN","value":"example.invalid"}]}
@@ -0,0 +1 @@
{"deleted":1}
@@ -0,0 +1 @@
{"batch_id":"00000000-0000-4000-8000-000000000040","task_ids":["00000000-0000-4000-8000-000000000041"]}
@@ -0,0 +1 @@
{"batch_id":"00000000-0000-4000-8000-000000000010","task_ids":["00000000-0000-4000-8000-000000000011"]}
@@ -0,0 +1 @@
{"task_ids":["00000000-0000-4000-8000-000000000011"]}
@@ -0,0 +1 @@
{"batch_id":"00000000-0000-4000-8000-000000000010","status_counts":{"COMPLETED":1,"FAILED":0,"PENDING":0,"PROCESSING":0}}
@@ -0,0 +1 @@
{"task_id":"00000000-0000-4000-8000-000000000011","status":"COMPLETED","result":{"people":[{"name":"Treg Example","emails":["person@example.com"],"phones":[]}]}}
@@ -0,0 +1 @@
{"batch_id":"00000000-0000-4000-8000-000000000020","task_ids":["00000000-0000-4000-8000-000000000021"]}
+1
View File
@@ -33,6 +33,7 @@ credit_rates_usd:
dropleads: {usd: 0.018, basis: "Pay As You Go: $18 / 1,000 non-expiring credits = $0.018 per credit. Promotional free credits do not set the platform rate", source: "https://dropleads.io/pricing", checked: "2026-09-15"}
sumble: {usd: 0.01, basis: "Pro monthly subscription: $99 / 9900 credits = $0.01 per credit, before configured platform margin. Retain this list rate when negotiating a custom plan.", source: "https://sumble.com/pricing", checked: "2026-09-09"}
moltsets: {usd: 0.01, kind: treg_shared_plan, fee_usd_month: 27, basis: "treg shared-plan rate: vendor charges $27/mo and the verified paid plan includes 5,000 enrichment records/week (conservatively 20,000 per four-week month); $0.01 per successful ordinary record sets break-even at 2,700 calls/mo, or 13.5% utilization, before configured platform margin.", source: "https://moltsets.com/pricing", checked: "2026-09-16"}
openmart: {usd: 0.0298, basis: "active Starter subscription $149/mo / 5,000 credits = $0.0298 per credit; this is the replacement cost of the account treg holds, with no auto-top-up", source: "https://www.openmart.com/pricing and the Openmart account subscription", checked: "2026-09-17"}
quickenrich: {usd: 0.004834, basis: "Starter monthly subscription: $29 / 6000 credits = $0.004833333 per credit, rounded up to 4834 micro-USD. treg base list rate before configured platform margin, assuming all monthly credits are used; unused credits increase effective cost. Free, Starter and Growth use subscription allowances. Unlimited-plan API status is not yet verified", source: "https://quickenrich.io/", checked: "2026-09-09"}
prospeo: {usd: 0.0245, basis: "Starter monthly subscription: $49 / 2,000 credits = $0.0245 per credit, before configured platform margin and assuming all monthly credits are used; unused credits increase effective cost", source: "https://prospeo.io/pricing", checked: "2026-09-16"}
aiark: {usd: 0.005267, basis: "Assigned monthly subscription: $79 / 15,000 credits = $0.005266666 per credit, rounded up to 5,267 micro-USD. treg base rate before configured platform margin, assuming all monthly credits are used; unused credits roll over up to twice the monthly allowance", source: "assigned AI Ark account and https://ai-ark.com/pricing", checked: "2026-09-17"}
+419
View File
@@ -0,0 +1,419 @@
# Openmart API. Live-verified 2026-09-17 with synthetic inputs.
# Synchronous data reads whose price and returned-record shape are proven can use either a team's
# own key or treg's metered platform key. Openmart rounds 0.3-credit record prices up to whole
# credits per operation; the call runtime applies that provider-specific rule and caps shared-key
# requests at 25 records. Async lifecycles, account mutations, and unresolved prices remain
# BYOK-only. The shared balance endpoint is internal capacity infrastructure, not a tool.
provider: openmart
source:
docs: https://app.openmart.com/api-docs
curated: '2026-09-17'
pricing_url: https://www.openmart.com/pricing
limits: Search, enrich, and task reads are documented at 20 requests/second; technology tasks at 15; other batch enrichment at 25.
proposed_capabilities:
companies.businesses.search: Search physical business locations.
companies.businesses.search.ids: Search and return only provider business IDs.
companies.lookup: Retrieve company records by provider identifier.
companies.technologies: Detect technologies used by a company.
companies.email.find: Find company-level email addresses.
companies.tasks.status: Read an asynchronous company-task batch status.
companies.tasks.list: List task identifiers in an asynchronous company batch.
companies.tasks.get: Read one asynchronous company-task result.
companies.deny-rules.create: Add private company-account deny rules.
companies.deny-rules.check: Check private company-account deny rules.
companies.deny-rules.delete: Delete private company-account deny rules.
people.find: Find people associated with a company.
endpoints:
- id: openmart.businesses.search
domain: companies
capability: companies.businesses.search
platform: companies
scope: any_account
method: POST
path: /api/v1/search
name: Search businesses
summary: Search local businesses with text, location, category, and attribute filters
input: &search_input
bodyType: json
body:
query: {type: string}
limit: {type: integer, min: 1, max: 1000}
estimate_total: {type: boolean}
test_request: {body: {query: treg-nonexistent-20260917, limit: 1, estimate_total: true}}
cost:
type: per_result
value: 0.3
currency: credit
per: 1
unit: record
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Openmart support confirmed 3 credits per 10 returned records, rounded up per operation; the effective charge is ceil(0.3 × returned records), and zero records cost zero. Ten live one-result calls each deducted one whole credit. Shared-key calls require an explicit limit from 1 to 25; own-key calls keep the upstream limit.
verified: '2026-09-17'
example_response: examples/openmart.businesses.search.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.businesses.search.ids
domain: companies
capability: companies.businesses.search.ids
platform: companies
scope: any_account
method: POST
path: /api/v1/search/only_ids
name: Search business IDs
summary: Search businesses and return only Openmart IDs
input: *search_input
test_request: {body: {query: treg-nonexistent-20260917, limit: 1}}
cost:
type: per_result
value: null
currency: credit
per: 1
unit: result
source: observed
source_url: https://app.openmart.com/api-docs
checked: '2026-09-17'
confidence: unknown
note: Documentation says reduced billing; live 1-row, 10-row, and cursor calls moved no whole credit, so no fractional rate is claimed.
platform_blocked: The upstream rate is unresolved; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.businesses.search.ids.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.businesses.lookup.openmart
domain: companies
capability: companies.lookup
platform: companies
scope: any_account
method: GET
path: /api/v1/business_records/list/openmart_id
name: Get businesses by Openmart ID
summary: Get up to 100 full business records by Openmart ID
input:
bodyType: json
note: The upstream request body is a raw JSON array of 1 to 100 Openmart ID strings.
# Openmart documents a JSON array on GET. Keep it in the input contract, but omit it from the
# generated smoke command because treg intentionally never prints GET commands with bodies.
test_request: {body: [00000000-0000-4000-8000-000000000001]}
cost: &lookup_record_cost
type: per_result
value: 0.3
currency: credit
per: 1
unit: record
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: documented
note: Full company records use the same ceil(0.3 × returned records) per-operation charge; omitted IDs are free because the response map contains no value for them. Shared-key calls accept at most 25 IDs; own-key calls keep the upstream limit.
verified: '2026-09-17'
example_response: examples/openmart.businesses.lookup.openmart.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.businesses.lookup.google-place
domain: companies
capability: companies.lookup
platform: companies
scope: any_account
method: GET
path: /api/v1/business_records/list/google_place_id
name: Get businesses by Google Place ID
summary: Get up to 100 full business records by Google Place ID
input:
bodyType: json
note: The upstream request body is a raw JSON array of 1 to 100 Google Place ID strings.
test_request: {body: [ChIJ00000000000000000000000]}
cost: *lookup_record_cost
verified: '2026-09-17'
example_response: examples/openmart.businesses.lookup.google-place.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.companies.enrich
domain: companies
capability: companies.enrich
platform: companies
scope: any_account
method: POST
path: /api/v1/enrich_company
name: Enrich companies
summary: Find matching business records from websites or social URLs
input:
bodyType: json
body:
website: {type: string}
social_media_link: {type: string}
limit: {type: integer, min: 1, max: 50}
estimate_total: {type: boolean}
test_request: {body: {website: example.invalid, limit: 1}}
cost:
type: per_result
value: 0.3
currency: credit
per: 1
unit: record
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Openmart support confirmed ceil(0.3 × returned records) per operation; a no-match returns an empty array and costs zero. Shared-key calls require an explicit limit from 1 to 25; own-key calls keep the upstream limit.
verified: '2026-09-17'
example_response: examples/openmart.companies.enrich.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.companies.search
domain: companies
capability: companies.search
platform: companies
scope: any_account
method: POST
path: /api/v2/brands/search
name: Search companies
summary: Search company brands with nested filters and cursor pagination
input:
bodyType: json
body:
search_param: {type: object, additionalProperties: true}
pagination: {type: object, additionalProperties: true}
test_request: {body: {search_param: {search_term: coffee shop, location: [{country: US}]}, pagination: {limit: 10}}}
cost:
type: per_result
value: 0.3
currency: credit
per: 1
unit: record
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Openmart support confirmed ceil(0.3 × returned records) per operation; brand rows are returned in data[], and an empty page costs zero. Shared-key calls require pagination.limit from 1 to 25; own-key calls keep the upstream limit.
verified: '2026-09-17'
example_response: examples/openmart.companies.search.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.people.find.batch
domain: contacts
capability: people.find
platform: people
scope: any_account
method: POST
path: /api/v1/task/batch/find_people
name: Find decision makers
summary: Start up to 100 decision-maker tasks for businesses
input: &task_batch_input
bodyType: json
body:
tasks: {type: array, items: {type: object, additionalProperties: true}, minItems: 1, maxItems: 100}
test_request: {body: {tasks: [{website: example.invalid, max_k: 1}]}}
cost:
type: per_result
value: 11
currency: credit
per: 1
unit: result
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Email costs 3 credits and phone costs 8; requesting both can cost 11 per found person. A live both-fields task reserved 3 at submit and charged the remainder asynchronously.
platform_blocked: Async fan-out, mixed outcomes, and delayed charges cannot be assigned safely; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.people.find.batch.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.technologies.find.batch
domain: companies
capability: companies.technologies
platform: companies
scope: any_account
method: POST
path: /api/v1/task/batch/find_tech
name: Find technologies
summary: Start up to 100 technology-detection tasks
input: *task_batch_input
test_request: {body: {tasks: [{website: example.invalid}]}}
cost:
type: per_result
value: 2
currency: credit
per: 1
unit: result
source: observed
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Two credits per completed result, reconciled from the live batch and the published $6 per 100 rate.
platform_blocked: Async batch ownership and delayed per-result charging cannot be assigned safely; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.technologies.find.batch.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.companies.email.find.batch
domain: contacts
capability: companies.email.find
platform: companies
scope: any_account
method: POST
path: /api/v1/task/batch/lookup_business_email
name: Find company emails
summary: Start up to 100 company-email lookup tasks
input: *task_batch_input
test_request: {body: {tasks: [{website: example.invalid}]}}
cost:
type: per_result
value: 0.3
currency: credit
per: 1
unit: result
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: documented
note: Dashboard price is 0.3 credit per returned company email. Integer balance deltas hide fractional usage and are not settlement evidence.
platform_blocked: Async ownership and delayed charging make this unsafe for a shared key; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.companies.email.find.batch.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.people.enrich.batch
domain: contacts
capability: people.enrich
platform: people
scope: any_account
method: POST
path: /api/v1/task/batch/lookup_people
name: Enrich known people
summary: Start email or phone enrichment for known people
input: *task_batch_input
test_request: {body: {tasks: [{people: [{name: Treg Example}], info_access: [EMAIL]}]}}
cost:
type: per_result
value: 11
currency: credit
per: 1
unit: result
source: docs
source_url: https://www.openmart.com/pricing
checked: '2026-09-17'
confidence: verified
note: Email costs 3 credits and phone costs 8; callers choose EMAIL, PHONE, or both.
platform_blocked: Mixed requested fields and asynchronous per-person results cannot be settled safely; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.people.enrich.batch.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.tasks.batch.status
domain: async
capability: companies.tasks.status
platform: companies
scope: own_account
method: GET
path: /api/v1/task/batch/{batch_id}/status
name: Get batch status
summary: Get status counts for an Openmart batch
input: &batch_id_input
pathParams:
batch_id: {type: string}
test_request: {pathParams: {batch_id: 00000000-0000-4000-8000-000000000000}}
cost: &free_task_read {type: free, value: 0, currency: USD, unit: call, source: observed, checked: '2026-09-17', confidence: verified, note: Live task reads used no credits.}
platform_blocked: Batch ownership is account-scoped; connect the Openmart key that created it.
verified: '2026-09-17'
example_response: examples/openmart.tasks.batch.status.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.tasks.batch.ids
domain: async
capability: companies.tasks.list
platform: companies
scope: own_account
method: GET
path: /api/v1/task/batch/{batch_id}/task_ids
name: List batch task IDs
summary: List task IDs in a batch, optionally filtered by status
input:
pathParams: {batch_id: {type: string}}
query:
status: {type: string, enum: [PENDING, PROCESSING, COMPLETED, FAILED]}
test_request: {pathParams: {batch_id: 00000000-0000-4000-8000-000000000000}, query: {status: COMPLETED}}
cost: *free_task_read
platform_blocked: Batch ownership is account-scoped; connect the Openmart key that created it.
verified: '2026-09-17'
example_response: examples/openmart.tasks.batch.ids.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.tasks.get
domain: async
capability: companies.tasks.get
platform: companies
scope: own_account
method: GET
path: /api/v1/task/{task_id}
name: Get task result
summary: Get one asynchronous task and its result
input:
pathParams: {task_id: {type: string}}
test_request: {pathParams: {task_id: 00000000-0000-4000-8000-000000000000}}
cost: *free_task_read
platform_blocked: Task ownership is account-scoped; connect the Openmart key that created it.
verified: '2026-09-17'
example_response: examples/openmart.tasks.get.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.deny-rules.create
domain: governance
capability: companies.deny-rules.create
platform: companies
scope: own_account
method: POST
path: /api/v2/deny-rules
name: Create deny rules
summary: Add domains, emails, or other values to the account deny list
input: &deny_input
bodyType: json
body:
rules: {type: array, items: {type: object, additionalProperties: true}, minItems: 1}
test_request: {body: {rules: [{type: DOMAIN, value: example.invalid}]}}
cost: &free_account_write {type: free, value: 0, currency: USD, unit: call, source: observed, checked: '2026-09-17', confidence: verified, note: Deny-rule calls used no credits.}
cache: forbidden
platform_blocked: This mutates account state; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.deny-rules.create.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.deny-rules.check
domain: governance
capability: companies.deny-rules.check
platform: companies
scope: own_account
method: POST
path: /api/v2/deny-rules/check
name: Check deny rules
summary: Check values against the account deny list
input: *deny_input
test_request: {body: {rules: [{type: DOMAIN, value: example.invalid}]}}
cost: *free_account_write
cache: forbidden
platform_blocked: The answer depends on private account state; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.deny-rules.check.json
docs_url: https://app.openmart.com/api-docs
- id: openmart.deny-rules.delete
domain: governance
capability: companies.deny-rules.delete
platform: companies
scope: own_account
method: DELETE
path: /api/v2/deny-rules
name: Delete deny rules
summary: Delete values from the account deny list
input: *deny_input
test_request: {body: {rules: [{type: DOMAIN, value: example.invalid}]}}
cost: *free_account_write
cache: forbidden
platform_blocked: This destructive operation mutates account state; connect your own Openmart key.
verified: '2026-09-17'
example_response: examples/openmart.deny-rules.delete.json
docs_url: https://app.openmart.com/api-docs
+1
View File
@@ -182,6 +182,7 @@ class Settings(BaseSettings):
platform_key_hunter: str = ""
platform_key_sumble: str = "" # Bearer; Pro monthly credits, optional vendor auto-top-up
platform_key_moltsets: str = "" # Bearer; shared subscription fair-use pools, no auto-top-up
platform_key_openmart: str = "" # Bearer; monthly subscription credits, no auto-top-up
platform_key_harvestapi: str = "" # X-API-Key; prepaid USD wallet
platform_key_dropleads: str = "" # X-API-Key; PAYG credits priced in fx.yaml
platform_key_quickenrich: str = "" # Bearer; monthly subscription credits, not auto-top-up
+12
View File
@@ -130,6 +130,17 @@ async def _moltsets(c, key):
}
async def _openmart(c, key):
d = await _get(c, "https://api.openmart.ai/api/v2/credit-balance",
headers={"Authorization": f"Bearer {key}"})
balance = d.get("balance") if isinstance(d, dict) else None
if type(balance) is not int or balance < 0:
balance = None
period_end = d.get("period_end") if isinstance(d, dict) else None
return {"value": balance, "unit": "credits",
"note": f"Monthly subscription balance; current period ends {period_end or 'at the account renewal date'}."}
async def _harvestapi(c, key):
d = await _get(c, "https://api.harvestapi.io/users/my-api-user",
headers={"X-API-Key": key})
@@ -623,6 +634,7 @@ BALANCE_ROUTES = {
"getleadsio": _getleadsio,
"sumble": _sumble,
"moltsets": _moltsets,
"openmart": _openmart,
"trykitt": _trykitt,
"contactout": _contactout,
"millionverifier": _millionverifier,
+4
View File
@@ -40,6 +40,7 @@ _KNOWN: dict[str, tuple[str, str, str]] = {
"getleadsio": ("credits", "manual", "api"),
"sumble": ("monthly_quota", "quota_reset", "api"),
"moltsets": ("rolling_quota", "subscription", "api"),
"openmart": ("credits", "subscription", "api"),
"scrubby": ("credits", "manual", "manual"),
"predictleads": ("monthly_quota", "quota_reset", "api"),
"companyenrich": ("credits", "manual", "api"),
@@ -95,6 +96,9 @@ _RATE_LIMITS: dict[str, dict] = {
# Routing-friendly shared-key pace. The 5,000-request/5h rolling allowance is capacity, not a
# burst rate; encoding it here would make the spacer add 3.6s before every routed attempt.
"moltsets": {"limit": 10, "window_s": 1, "source": "policy"},
# The provider publishes 15-25 requests/s by endpoint family. Use the strictest ceiling while
# smoothing remains provider-wide. All direct tools are BYOK-only today.
"openmart": {"limit": 15, "window_s": 1, "source": "docs"},
"sumble": {"limit": 10, "window_s": 1, "source": "docs"},
"scrubby": {"limit": 25, "window_s": 1, "source": "docs"},
"leadsforge": {"limit": 120, "window_s": 60, "source": "headers"},
+4 -1
View File
@@ -1310,7 +1310,10 @@ def call_template(ep: dict) -> str:
# arguments but still requires a JSON body — and dropping `--data '{}'` from the line hands the
# reader a command that differs from the one that was tested, on handlers that reject an empty
# body outright.
if body is not None and (body or ep["method"] != "GET"):
# treg can faithfully relay a caller-supplied GET body, but generated shell commands never add
# one: too many clients/proxies silently discard it. The stored test request can still preserve
# the provider's unusual verification contract without printing a misleading paste-ready line.
if body is not None and ep["method"] != "GET":
parts += ["--data", shlex.quote(json.dumps(
body, separators=(",", ":"), ensure_ascii=False))]
return " ".join(parts)
+20 -1
View File
@@ -1330,6 +1330,23 @@ MOLTSETS = OAuthProvider(
# Live 2026-09-16: bogus Bearer 401; valid key 200. Account calls consume no records.
)
OPENMART = OAuthProvider(
service="openmart", display_name="Openmart", auth_kind="key",
token_label="API key", token_placeholder="your Openmart API key",
token_header="Authorization", token_format="Bearer {secret}",
setup_url="https://app.openmart.com/",
setup_action_label="Get your Openmart API key",
setup_steps=("Sign in to Openmart and open the API settings.",
"Create an API key and copy it."),
setup_note="Searches and enrichment consume account credits. Batch tasks can charge after submission; connection verification is free.",
auth_uri="", token_uri="", scopes={}, client_id_setting="", client_secret_setting="",
category="Enrichment",
summary="Search and enrich businesses, find decision makers and contact details, and detect technologies.",
base_url="https://api.openmart.ai", docs_url="https://app.openmart.com/api-docs",
probe_path="/api/v2/credit-balance", probe_method="GET",
# Live 2026-09-17: missing and bogus Bearer keys returned 401; a valid key returned 200.
)
HARVESTAPI = OAuthProvider(
service="harvestapi", display_name="HarvestAPI", auth_kind="key",
token_label="API key", token_placeholder="your HarvestAPI API key",
@@ -3172,7 +3189,9 @@ REGISTRY: dict[str, OAuthProvider] = {
GOOGLE_ADS, YOUTUBE,
LINKEDIN, SLACK, X, TIKTOK, FACEBOOK, INSTAGRAM, META_ADS,
# API-key providers
APOLLO, PDL, AKTA, HUNTER, SUMBLE, MOLTSETS, HARVESTAPI, DROPLEADS, QUICKENRICH, PROSPEO, AIARK, WIZA, GETLEADSIO, SCRUBBY, ZEROBOUNCE, TRYKITT, CONTACTOUT, MILLIONVERIFIER, BOUNCEBAN, CRUNCHBASE, MINIMAX, OPENROUTER, REPLICATE,
APOLLO, PDL, AKTA, HUNTER, SUMBLE, MOLTSETS, OPENMART, HARVESTAPI, DROPLEADS,
QUICKENRICH, PROSPEO, AIARK, WIZA, GETLEADSIO, SCRUBBY, ZEROBOUNCE, TRYKITT,
CONTACTOUT, MILLIONVERIFIER, BOUNCEBAN, CRUNCHBASE, MINIMAX, OPENROUTER, REPLICATE,
REAPI, PIAPI,
TIKHUB, BRIGHTDATA, SEMRUSH, JUSTONEAPI,
SCRAPECREATORS,
+3
View File
@@ -138,6 +138,9 @@ CATALOG: list[dict] = [
"auth": {"shape": "bearer"}},
{"provider": "MoltSets", "tokens": ["MOLTSETS"],
"base_url": "https://api.moltsets.com/api/v1/tools", "auth": {"shape": "bearer"}},
{"provider": "Openmart", "tokens": ["OPENMART"],
"base_url": "https://api.openmart.ai", "auth": {"shape": "bearer"},
"probe": "api/v2/credit-balance"},
{"provider": "Crustdata", "tokens": ["CRUSTDATA"], "base_url": "https://api.crustdata.com",
"auth": {"shape": "bearer"}, "probe": "account/credits",
"required_headers": {"x-api-version": "2025-11-01"}},
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 139 KiB

+3 -1
View File
@@ -53,7 +53,9 @@ for _k in (
# without this a suite run on their laptop could resolve tier 4 and spend actual money on the
# in-process upstream's echo. Tests that exercise tier 4 set both halves via monkeypatch.
"PLATFORM_KEY_TRYKITT", "PLATFORM_PROVIDERS", "PLATFORM_KEY_TIKHUB", "PLATFORM_KEY_DATAFORSEO", "PLATFORM_KEY_SCRAPECREATORS",
"PLATFORM_KEY_QUICKENRICH", "PLATFORM_KEY_PROSPEO", "PLATFORM_KEY_AIARK", "PLATFORM_KEY_WIZA", "PLATFORM_KEY_SCRUBBY", "PLATFORM_KEY_ZEROBOUNCE", "PLATFORM_KEY_SUMBLE", "PLATFORM_KEY_MOLTSETS", "PLATFORM_KEY_HARVESTAPI", "PLATFORM_KEY_DROPLEADS",
"PLATFORM_KEY_QUICKENRICH", "PLATFORM_KEY_PROSPEO", "PLATFORM_KEY_AIARK", "PLATFORM_KEY_WIZA",
"PLATFORM_KEY_SCRUBBY", "PLATFORM_KEY_ZEROBOUNCE", "PLATFORM_KEY_SUMBLE", "PLATFORM_KEY_MOLTSETS",
"PLATFORM_KEY_OPENMART", "PLATFORM_KEY_HARVESTAPI", "PLATFORM_KEY_DROPLEADS",
"PLATFORM_KEY_FINANCIALDATASETS",
):
os.environ[f"TREG_{_k}"] = "" # the test upstream is an in-process ASGI transport, not real DNS
+24
View File
@@ -14,6 +14,30 @@ import httpx
import pytest
async def test_openmart_balance_collector_and_policy():
def probe(request):
assert request.method == "GET"
assert request.url.path == "/api/v2/credit-balance"
assert request.headers["authorization"] == "Bearer test"
return httpx.Response(200, json={
"balance": 4800,
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-10-01T00:00:00Z",
})
async with httpx.AsyncClient(transport=httpx.MockTransport(probe)) as client:
row = await collectors._openmart(client, "test")
assert row == {
"value": 4800,
"unit": "credits",
"note": "Monthly subscription balance; current period ends 2026-10-01T00:00:00Z.",
}
capacity = policy.default_policy("openmart", has_key=True)
assert capacity.capacity_type == "credits"
assert capacity.funding_mode == "subscription"
assert capacity.rate_limit == {"limit": 15, "window_s": 1, "source": "docs"}
@pytest.mark.parametrize("value,expected", [(0, 0), (71, 71), ("5000", 5000)])
async def test_zerobounce_balance_accepts_nonnegative_integer_values(monkeypatch, value, expected):
monkeypatch.setenv("TREG_PLATFORM_KEY_ZEROBOUNCE", "private-test-key")
+1
View File
@@ -264,6 +264,7 @@ _UNRECORDED_SIGNATURE = {
"bounceban", # verification credits remain; exhaustion was not forced and no overflow is claimed
"zerobounce", # credits remain; exhaustion was not forced and no overflow is claimed
"moltsets", # rolling allowance exhaustion was not forced; no overflow route claimed
"openmart", # funded subscription was not exhausted; shared-key exhaustion was not forced
# Bare 402 is already the generic balance signal; the real empty-Credits body was not forced.
"financialdatasets",
"minimax", "oceanio", "openrouter", "replicate", "scrapecreators", "seranking",
+34
View File
@@ -19,6 +19,40 @@ from treg.domain.catalog import store as cs
from treg import oauth_providers as P
def test_openmart_surface_separates_platform_reads_from_byok_lifecycles():
cat = cs.load()
rows = cat.for_provider("openmart")
assert len(rows) == 16
assert len({(e["method"], e["path"]) for e in rows}) == 16
assert {e["id"] for e in rows if cat.platform_eligible(e)} == {
"openmart.businesses.search",
"openmart.businesses.lookup.openmart",
"openmart.businesses.lookup.google-place",
"openmart.companies.enrich",
"openmart.companies.search",
}
assert all(e.get("verified") and e.get("example_file") for e in rows)
assert cat.credit_rates["openmart"] == .0298
assert "openmart.account.balance" not in cat.by_id
def test_openmart_pricing_and_lifecycle_boundaries_stay_visible():
cat = cs.load()
assert cat.by_id["openmart.people.find.batch"]["cost"]["value"] == 11
assert cat.by_id["openmart.technologies.find.batch"]["cost"]["value"] == 2
company_email = cat.by_id["openmart.companies.email.find.batch"]
assert company_email["cost"]["value"] == .3
assert company_email["cost"]["confidence"] == "documented"
fast = cat.by_id["openmart.businesses.search.ids"]
assert fast["cost"]["value"] is None and fast["cost"]["confidence"] == "unknown"
assert all(cat.by_id[key]["scope"] == "own_account" for key in (
"openmart.tasks.batch.status", "openmart.tasks.batch.ids", "openmart.tasks.get",
"openmart.deny-rules.create", "openmart.deny-rules.check",
"openmart.deny-rules.delete",
))
assert cat.by_id["openmart.deny-rules.delete"]["cache"] == "forbidden"
# ---- platform listing --------------------------------------------------------------------
async def test_platforms_lists_the_curated_shelves_busiest_first(clients: AsyncClient):
r = await clients.get("/catalog/platforms")
+37 -1
View File
@@ -23,7 +23,7 @@ from treg import oauth_providers as P
def test_key_providers_are_offerable_without_deployment_credentials():
"""The user brings the key, so treg holds no app of its own — a key provider must be offerable,
not shown as 'not configured' the way an unset OAuth provider is."""
for svc in ("apollo", "pdl", "akta", "hunter", "sumble", "moltsets", "harvestapi", "dropleads", "quickenrich", "prospeo", "aiark", "wiza", "getleadsio", "scrubby", "zerobounce", "contactout", "millionverifier", "bounceban", "trykitt", "crunchbase", "tikhub", "brightdata", "semrush",
for svc in ("apollo", "pdl", "akta", "hunter", "sumble", "moltsets", "openmart", "harvestapi", "dropleads", "quickenrich", "prospeo", "aiark", "wiza", "getleadsio", "scrubby", "zerobounce", "contactout", "millionverifier", "bounceban", "trykitt", "crunchbase", "tikhub", "brightdata", "semrush",
"justoneapi", "dataforseo", "seranking", "moz", "majestic", "serpstat", "exa",
"cloro",
"lusha", "coresignal", "diffbot", "thecompaniesapi", "leadmagic", "fiber-ai",
@@ -59,6 +59,42 @@ def test_key_providers_appear_in_the_marketplace_listing():
assert "Market data" in P.CATEGORY_ORDER
def test_openmart_registry_uses_the_free_balance_probe_and_bearer_key(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_OPENMART", "PLATFORM-OPENMART")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "openmart")
provider = P.get("openmart")
settings = Settings(_env_file=None)
assert provider.base_url == "https://api.openmart.ai"
assert provider.probe_path == "/api/v2/credit-balance"
assert provider.probe_method == "GET"
assert settings.platform_key_for("openmart") == "PLATFORM-OPENMART"
assert P.platform_bindings(provider) == [{
"platform_setting": "platform_key_openmart",
"injector": "env",
"location": "header",
"name": "Authorization",
"format": "Bearer {secret}",
}]
async def test_openmart_connect_rejects_bad_key_and_accepts_valid_key(clients, monkeypatch):
def probe(request):
assert request.method == "GET"
assert request.url.path == "/api/v2/credit-balance"
if request.headers["authorization"] == "Bearer bad":
return httpx.Response(401, json={"detail": "Invalid API Key"})
return httpx.Response(200, json={"balance": 4800})
async with AsyncClient(transport=httpx.MockTransport(probe)) as upstream:
monkeypatch.setattr(app.state, "http", upstream)
bad = await clients.post(
"/connections/token", json={"provider": "openmart", "token": "bad"})
assert bad.status_code == 422
good = await clients.post(
"/connections/token", json={"provider": "openmart", "token": "own-key"})
assert good.status_code == 200, good.text
def test_zerobounce_registry_uses_internal_usage_probe_and_query_key(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_ZEROBOUNCE", "PLATFORM-ZEROBOUNCE")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "zerobounce")
+126
View File
@@ -135,6 +135,15 @@ def diffbot_platform_on(monkeypatch):
get_settings.cache_clear()
@pytest.fixture
def openmart_platform_on(monkeypatch):
monkeypatch.setenv("TREG_PLATFORM_KEY_OPENMART", "PLATFORM-OPENMART")
monkeypatch.setenv("TREG_PLATFORM_PROVIDERS", "openmart")
get_settings.cache_clear()
yield
get_settings.cache_clear()
async def _balance(clients: AsyncClient) -> int:
org_id = (await clients.get("/orgs")).json()[0]["org_id"]
return (await clients.get(f"/orgs/{org_id}/balance")).json()["balance_micro"]
@@ -802,6 +811,123 @@ def _mk(provider: str, **kw) -> call_resolution.MarketplaceCall:
return call_resolution.MarketplaceCall(tool=None, upstream="", consumed=set(), provider=provider, **kw)
@pytest.mark.parametrize(("endpoint", "body", "credits"), [
("openmart.businesses.search", b'[]', 0),
("openmart.businesses.search", b'[{"id":"1"}]', 1),
("openmart.businesses.search", b'[{"id":"1"},{"id":"2"},{"id":"3"}]', 1),
("openmart.businesses.search", b'[{},{},{},{}]', 2),
("openmart.businesses.search", b'{"data":[{},{},{},{},{},{},{},{},{},{}]}', 3),
("openmart.companies.enrich", b'{"data":[]}', 0),
("openmart.companies.search", b'{"data":[{},{}]}', 1),
("openmart.businesses.lookup.openmart", b'{"a":{},"b":{}}', 1),
])
def test_openmart_settlement_rounds_three_credits_per_ten_records(endpoint, body, credits):
mk = _mk("openmart", endpoint_id=endpoint, cost_type="per_result", unit_micro=29_800)
assert call_settle._observed_cost_micro(mk, body) == credits * 29_800
def test_openmart_settlement_rejects_undocumented_response_shapes():
search = _mk("openmart", endpoint_id="openmart.businesses.search",
cost_type="per_result", unit_micro=29_800)
lookup = _mk("openmart", endpoint_id="openmart.businesses.lookup.openmart",
cost_type="per_result", unit_micro=29_800)
assert call_settle._observed_cost_micro(search, b'{"data":{}}') is None
assert call_settle._observed_cost_micro(lookup, b'[]') is None
@pytest.mark.parametrize(("endpoint", "body", "expected"), [
("openmart.businesses.search", {"query": "coffee", "limit": 1}, 29_800),
("openmart.businesses.search", {"query": "coffee", "limit": 4}, 59_600),
("openmart.companies.search", {"pagination": {"limit": 25}}, 238_400),
("openmart.businesses.lookup.openmart", ["a", "b", "c"], 29_800),
])
def test_openmart_reservations_use_the_same_whole_credit_rounding(endpoint, body, expected):
cat = catalog_store.load()
cost = cat.cost_view(cat.by_id[endpoint]["cost"], "openmart")
estimate, unit = call_resolution._marketplace_pricing(
"openmart", endpoint, cost, {}, json.dumps(body).encode())
assert estimate == expected
assert unit == 29_800
@pytest.mark.parametrize(("endpoint", "body"), [
("openmart.businesses.search", {"query": "coffee"}),
("openmart.businesses.search", {"query": "coffee", "limit": 26}),
("openmart.companies.search", {"pagination": {"limit": 26}}),
("openmart.businesses.lookup.openmart", [str(i) for i in range(26)]),
])
async def test_openmart_platform_calls_require_an_explicit_one_to_25_cap(
clients, openmart_platform_on, endpoint, body,
):
before = await _balance(clients)
response = await clients.request(
catalog_store.load().by_id[endpoint]["method"], f"/call/{endpoint}",
content=json.dumps(body), headers={"content-type": "application/json"},
)
assert response.status_code == 400, response.text
assert await _balance(clients) == before
assert not [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
async def test_openmart_platform_search_settles_from_returned_rows(
clients, openmart_platform_on, monkeypatch,
):
rows = [{"id": "1"}, {"id": "2"}, {"id": "3"}]
monkeypatch.setattr(call_service, "relay", _fake_relay(200, json.dumps(rows).encode()))
before = await _balance(clients)
response = await clients.post("/call/openmart.businesses.search", json={
"query": "coffee", "limit": 5,
})
assert response.status_code == 200, response.text
assert await _balance(clients) == before - 29_800
money = [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
assert [e["kind"] for e in money[:2]] == ["settle", "reserve"]
async def test_openmart_byok_keeps_upstream_limits_and_is_unmetered(clients, openmart_platform_on):
await clients.post("/secrets", json={"name": "openmart", "value": "OWN-OPENMART"})
before = await _balance(clients)
body = {"query": "coffee", "limit": 100}
response = await clients.post("/call/openmart.businesses.search", json=body)
assert response.status_code == 200, response.text
echoed = response.json()
assert echoed["auth"] == "Bearer OWN-OPENMART"
assert json.loads(echoed["body"]) == body
assert await _balance(clients) == before
assert not [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
async def test_openmart_byok_lookup_preserves_the_documented_get_array_body(
clients, openmart_platform_on,
):
await clients.post("/secrets", json={"name": "openmart", "value": "OWN-OPENMART"})
ids = ["00000000-0000-4000-8000-000000000001"]
response = await clients.request(
"GET", "/call/openmart.businesses.lookup.openmart",
content=json.dumps(ids), headers={"content-type": "application/json"},
)
assert response.status_code == 200, response.text
assert response.json()["auth"] == "Bearer OWN-OPENMART"
assert json.loads(response.json()["body"]) == ids
async def test_openmart_unpriced_fast_ids_are_blocked_before_relay_or_money(
clients, openmart_platform_on, monkeypatch,
):
before = await _balance(clients)
async def forbidden(*args, **kwargs):
raise AssertionError("platform guard must precede relay")
monkeypatch.setattr(call_service, "relay", forbidden)
response = await clients.post("/call/openmart.businesses.search.ids", json={
"query": "coffee", "limit": 1,
})
assert response.status_code == 404, response.text
assert await _balance(clients) == before
assert not [e for e in await _entries(clients) if e["kind"] in ("reserve", "settle", "release")]
def test_observed_cost_only_trusts_a_real_number():
"""A missing, non-numeric or negative charge means "we never learned it" — settle at the estimate.
A reported ZERO is different: the provider is saying it did not charge, and is honoured."""
+3 -1
View File
@@ -1278,7 +1278,9 @@ async def test_search_survives_missing_a_few_words_of_an_agent_sentence(clients)
# single letters can never select: "K&L" must not let k + l decide admission, and the company
# job ("enrich by name") must lead instead of 67 rows of noise (logged miss, 2026-08-20)
rows, total = cs.search("K&L Gates company lookup", cat, 8)
assert 0 < total < 30 and rows[0][0]["capability"].startswith("companies.")
# Provider growth can add a few legitimate company-lookup rows. Keep the guard tight enough
# to reject single-letter noise without treating new lookup providers as false positives.
assert 0 < total < 35 and rows[0][0]["capability"].startswith("companies.")
# the jobs rows must survive an industry qualifier the catalog never says ("law firm"), via
# the openings->postings and firm->company aliases (logged miss, 2026-08-20)
rows, total = cs.search("law firm job openings hiring signal", cat, 8)
+1 -1
View File
@@ -47,7 +47,7 @@ def test_every_provider_is_registered():
"google-ads", "youtube", "linkedin", "slack", "x", "tiktok",
"facebook", "instagram", "meta-ads",
# API-key providers (auth_kind="key")
"apollo", "pdl", "akta", "hunter", "sumble", "moltsets", "harvestapi", "dropleads", "quickenrich", "prospeo", "aiark", "wiza", "getleadsio", "scrubby", "zerobounce", "contactout", "millionverifier", "bounceban", "trykitt", "crunchbase", "tikhub", "brightdata", "semrush", "justoneapi",
"apollo", "pdl", "akta", "hunter", "sumble", "moltsets", "openmart", "harvestapi", "dropleads", "quickenrich", "prospeo", "aiark", "wiza", "getleadsio", "scrubby", "zerobounce", "contactout", "millionverifier", "bounceban", "trykitt", "crunchbase", "tikhub", "brightdata", "semrush", "justoneapi",
"scrapecreators",
"dataforseo", "seranking", "moz", "majestic", "serpstat", "exa",
"cloro",
+9
View File
@@ -103,6 +103,15 @@ def test_every_shipped_adapter_round_trips_its_fixture():
assert ad.is_miss({"email": None}) and not ad.is_miss({"email": "x"})
def test_openmart_company_search_is_routed_but_company_enrich_is_not():
cat = catalog_store.load()
adapter = cat.adapters["openmart.companies.search"]
assert adapter.verified, adapter.verify_note
assert "openmart.companies.search" in cat.by_id["treg.companies.search"]["routed_children"]
assert cat.platform_eligible(cat.by_id["openmart.companies.search"])
assert "openmart.companies.enrich" not in cat.by_id["treg.companies.enrich"]["routed_children"]
def test_dropleads_routing_surface_contains_only_verified_single_record_tools():
catalog = catalog_store.load()
expected = {