Gandi anonymous domain search research
Gandi anonymous domain search research Tested 2026-10-01 from the dev container. The provider and Hosting skill are deployed to dev. REST, CLI, npm and pip i...
Gandi anonymous domain search research
Tested 2026-10-01 from the dev api container. The provider and Hosting skill
are deployed to dev. REST, CLI, npm and pip integration checks passed in isolated
CI, with real requirements ranking and a real naming chat verified on dev.
The Workflow capability also passed a complete isolated CLI run, including
typed downstream checks and encrypted chat delivery. See the
Workflow CI result.
The anonymous shop endpoints are not a supported Gandi API contract.
Live evidence
Probe fixtures
contain 36 paced requests, including full public synthetic responses. The
probe script uses async
httpx, disables environment proxies, creates fresh clients without Gandi
cookies/authorization, and loads existing Webshare credentials only in memory.
| Route | Requests accepted | Complete lookup/SSE responses | Median lookup | Median SSE |
|---|---|---|---|---|
| Direct dev-server egress | 18/18 HTTP 200 | 17/18 | 0.425 s | 1.413 s |
| Existing Webshare rotating residential proxy | 18/18 HTTP 200 | 17/18 | 1.046 s | 6.561 s |
The one incomplete case on each route was the exploratory, unsupported encoding
tlds=com,net: the server opened SSE, returned no suggestions, then stalled.
Both requests hit the 15-second idle timeout. All other tested encodings
completed. Complete responses include intentional negative cases, not only
available domains. This small sample does not establish uptime, capacity, or
published rate limits. Requests were sequential with a one-second pause; no
load test, registration, account access, or infrastructure mutation was performed.
Cases: registered example.com; three repeated synthetic .com lookups;
USD/US .net; Unicode bücher-probe-20261001.com; .ai minimum term;
unsupported .invalid; a domain containing a space; repeated keyword,
multiword and exact-word searches; single/repeated/comma TLD filters; and
the public premium domain cedar.online.
Endpoints and request fields
| Method | URL | Format | Authentication |
|---|---|---|---|
| GET | https://api.gandi.net/v5/domain/check?name=example.com |
Official REST | HTTP 401 without PAT/API key |
| GET | https://shop.gandi.net/api/v5/suggest/lookup |
JSON | No Gandi credentials or cookies in successful tests |
| GET | https://shop.gandi.net/api/v5/suggest/suggest |
text/event-stream |
No Gandi credentials or cookies in successful tests |
Common tested fields are search, currency, country, and grid=A.
Suggestions additionally accept lang=en, page=1, per_page=5,
source=shop, lock_sentence=false/true, phases=golive, and tlds.
The probe sent a browser-style User-Agent and shop Referer; SSE also sent
Accept: text/event-stream. These headers worked; their individual necessity
was not isolated. The first plain suggestion attempt in the initial research
received HTTP 403 abuse. An OpenMates browser Origin received HTTP 400 CSRF
and no CORS allowance. Requests belong on the server.
tlds=net returned .net results. Repeated tlds=com&tlds=net produced only
.com results in this sample. Do not promise a multi-TLD filter using that
encoding. Comma encoding stalled on both routes. Implement explicit-TLD name
checks with bounded exact lookups; keep suggestion searches to one upstream
TLD per request and enforce requested extensions on normalized results.
lock_sentence=true is not an exact-domain lookup: example.com still
returned other example.* domains. Both multiword modes returned individual
word suggestions, with different lists between routes. Use /lookup for an
explicit FQDN. Do not silently treat a natural-language product brief as a
Gandi name keyword; the AI should propose a short name/keyword first.
Response contract observed
Lookup returns fqdn, availability, numeric premium, and prices.
Unavailable results can have prices: {}. .invalid returned HTTP 200 with
availability: error; bad domain.com returned HTTP 200 with unavailable.
Validate names locally and distinguish unknown/error from unavailable.
Pricing contains currency, grid, taxes, and products. Products carry
process (create or renew), name, status, prices, and phases.
Standard product names are suffixes such as .com; premium product names can
be the complete domain. Attach prices to the lookup/event fqdn, not to
products[].name as the domain identity.
Each price tier contains duration_unit, min_duration, max_duration,
price_before_taxes, price_after_taxes, discount, optional normal prices,
type, options, and features. Preserve tiers and distinguish registration
from renewal: a normal registration price is not the renewal quote.
The official Domain API price schema
describes the duration range over which a price unit applies. The shop is a
separate undocumented interface, so checkout amounts remain indicative.
For synthetic .net, EUR/DE returned €14.27 for the one-year registration
tier and €47.60 for the one-year renewal tier, including 19% VAT. USD/US
returned $12.99 registration, $39.98 renewal, and an empty taxes list.
.ai had a two-year minimum for both processes: never label it a one-year
offer. cedar.online was premium and returned €356.36 registration and
€1,292.51 renewal, with a €1,200.50 normal registration price, including
German VAT. These are dated examples, not current purchase guarantees.
Suggestion SSE events observed:
| Event | Meaning / normalization |
|---|---|
suggest_meta |
TLD filter list, opaque search UUID, filter status |
pagination |
Raw count/page/next/last; raw count is not an available-domain count |
suggestions |
Ordered domain candidates with suffix, corporate flag, language support, categories, phase, restriction |
das, das_failed |
Availability/premium/reserved status for one FQDN |
billing, billing_failed |
Pricing or missing pricing for one FQDN |
bundle_available, bundle_pricing |
Shop bundles; outside initial skill scope |
tick |
Heartbeat, not result content |
done |
Stream completed |
Events arrive in different orders. Join by FQDN, preserve candidate order, and
require bounded stream completion. An available result can have no pricing.
An individual result can report error even when the overall stream completes.
cedarcom.et alternated between available and error. Never convert that error
to unavailable or fabricate a price. Preserve complete useful children if the
stream times out and mark the response partial.
Explicit lookup availability/pricing was consistent between direct and proxy
for the paired sample. Suggestions differed: the third cedarcomet candidate
was .fi directly and .nl/.yt via the proxy despite country=DE.
Treat ranking/localization as provider-dependent, not deterministic parity.
Proposed fallback and limits
Try direct first. On transport timeout, HTTP 403, 5xx, malformed response, or incomplete stream, allow one bounded attempt through the existing Webshare proxy. Preserve useful partial children and deduplicate by canonical FQDN. For per-domain transient errors retry only affected exact lookups, within the same attempt/result budget. A valid unavailable answer is a successful lookup. Local validation errors, an unsupported suffix, or missing price alone do not justify rotating the full search. Honor HTTP 429/Retry-After; do not rotate repeatedly to evade limits. No infinite retries or proxy cycling.
Proposed bounds: 1–5 grouped requests, default 10/max 20 results per request, at most two suggestion pages or 20 exact lookup candidates per group, two in-flight Gandi operations, and a 40-second deadline per group including fallback. Use an explicit timeout/content/event limit. Retain only known schema fields and build external links from the fixed Gandi shop origin.
Domain searches can be sensitive. Prefer request-scoped deduplication; do not put plaintext queries/results in a shared persistent cache or diagnostic logs. If a cross-request cache is later needed, its encryption and TTL require an explicit design. User-facing checked-at timestamps belong to encrypted embed content; transport mode remains operational metadata.
Credentials, privacy, and operating assumptions
No Gandi user account or key is required by the tested shop interface. Proxy
fallback uses the existing Vault path kv/data/providers/webshare, keys
proxy_username/proxy_password, with its rotating residential endpoint.
Never import an AI/app skill to obtain that helper; shared transport/secrets
logic belongs in backend/shared/. Gandi receives the selected name/keyword,
currency/country/language and server/proxy request metadata. Do not send chat
context, OpenMates identifiers, email, or user credentials. Webshare is an
additional network service; use HTTPS with normal certificate verification
and accurately document the proxy’s metadata exposure before launch.
Gandi’s authentication docs require authentication for its official API. Shop search has no published integration SLA, rate limit, or fee verified here. Proxy bandwidth has an existing service cost; do not assume it is free or add a Gandi API-key requirement.
Website terms, privacy policy, and shop robots.txt were inspected on 2026-10-01. Robots disallows crawl URLs containing several search-related parameters. The terms discuss restrictions on copying/redistributing website data; these tests establish technical feasibility, not permission for a commercial anonymous API integration. Record the intended use/terms decision before launch. No claim about mass WHOIS access is made: that is a separate service. Update Gandi and relevant Webshare privacy disclosures when implementation is approved; the policy URL above resolved to the actual Privacy Policy page.
Reproduce
From an authorized dev session, without printing secrets:
docker exec -i api python - --mode both < scripts/api_tests/test_gandi_search_probe.py
docker exec -i api python - --mode both --case registered --case premium < scripts/api_tests/test_gandi_search_probe.py
--list, --mode direct|proxy|both, and repeatable --case are supported.
There is no Gandi --api-key: the tested path is deliberately credential-free.
The script reports request/schema completeness separately from domain status.
The saved fixture includes two expected exploratory filter failures; a live
rerun is an external provider probe, not a product E2E pass.
Hosting skill contract
hosting/search_domains uses the existing authenticated OpenMates skill route.
Gandi itself needs no API key. Initial quote currencies are EUR and USD, which
were verified by the probe; the tax-country default is returned explicitly as DE.
{
"requests": [{
"id": "name-ideas",
"query": "cedarcomet",
"tlds": ["com", "net"],
"country": "DE",
"currency": "EUR",
"max_results": 10,
"availability": "prefer_available",
"relevance_criteria": "Prefer a short name with low evidenced renewal cost."
}]
}
POST this body to /v1/apps/hosting/skills/search_domains, or use:
openmates apps hosting search_domains --input '{"requests":[{"query":"cedarcomet","max_results":10}]}' --json
One app call accepts one to five independent searches in requests. Groups run
concurrently, share the two-operation provider limit, and preserve caller IDs
and response order. For example, check two exact domains in one CLI call:
openmates apps hosting search_domains --input '{"requests":[{"id":"first","query":"example.com","availability":"all"},{"id":"second","query":"example.net","availability":"all"}]}' --json
The npm SDK exposes client.apps.hosting.searchDomains(input); the pip SDK
exposes client.apps.hosting.search_domains(input). Both accept the same grouped
input. Each group defaults to ten selected results and permits at most twenty.
The default prefer_available selects available names first and fills a shortfall
with matching checked in-use domains. available_only never fills with in-use
domains. all preserves ranked available and in-use candidate order. Unknown
checks are diagnostic evidence and cannot fill result slots. Explicit FQDNs use
exact lookup, so an in-use exact query remains visible under the default policy.
Groups expose selected results bounded by max_results and a separate
checked_results pool bounded by 40. Quotes retain registration and renewal
tiers, original duration ranges, taxes, premium status and restrictions.
Nonblank relevance_criteria makes one bounded shared Jev decision after hard
filters, removes scores below one, and reports relevance_applied. Evaluator
failure preserves provider ordering and availability selection with a warning.
The implementation permits two simultaneous provider operations and a 40-second group deadline. Completed checks survive a deadline. Direct requests precede one lazy Webshare fallback; missing prices, valid in-use answers and rate limits do not trigger repeated proxy attempts. Proxy evidence cannot replace a confirmed status with an unknown check. Mismatched-currency prices remain unknown.
Programmatic coverage is in skill-hosting-search-api.spec.ts. The separate
scripts/api_tests/test_hosting_requirements.py requires real dev-server Jev
inference with disposable CLI state; replay does not satisfy that check.
Workflow composition
The Workflow capability is hosting.search_domains: synchronous, read-only,
unattended, with no connected account or approval required. Its requests input
is the same grouped contract as the REST and CLI skill route.
title: Check a product domain
start_when:
manual: {}
steps:
- id: domains
use_app_skill: hosting.search_domains
input:
requests:
- query: example.com
availability: available_only
max_results: 1
- id: report
send_chat_message:
title: Domain check
message: "Found {{steps.domains.result_count}} available domains."
Later steps can reference $nodes.domains.output.results or
$nodes.domains.output.result_count. The normalized list contains selected
domains across all groups. raw.results preserves each original group, including
checked candidates, complete quote tiers, warnings and partial errors. A completed
available-only search may have a zero result count; checked in-use evidence remains
in raw. The dedicated skill-hosting-search-workflow.spec.ts covers real CLI
step tests and a complete Workflow run against the anonymous Gandi endpoint.