Tool workflows
Three worked examples, one per tool family. A successful tool call returns its
payload twice over — as a text block and as structuredContent, with the same
content — so your client may render either. (A tool-level failure returns text
only, flagged as an error.) The examples below show the payload.
Discovery: call one operation
1. Find the operation
search_operations with:
{ "query": "renew domain", "limit": 10 }
comes back as:
{
"count": 2,
"total": 2,
"results": [
{
"operationId": "renew_domain_v1_domains__domain_reference__renew_post",
"method": "POST",
"path": "/v1/domains/{domain_reference}/renew",
"tags": ["domain"],
"summary": "Renew a domain",
"safety": { "read": false, "write": true, "cost": true, "destructive": false }
},
{
"operationId": "epp_check_domain_v1_domains_check_get",
"method": "GET",
"path": "/v1/domains/check",
"tags": ["domain"],
"summary": "Check domain availability and registration metadata",
"safety": { "read": true, "write": false, "cost": false, "destructive": false }
}
]
}
Two fields are worth understanding.
total counts the operations in the top relevance tier — those matching the
most of your query terms — including any beyond limit. So total larger than
count means the results were capped and the query should be narrowed. It is not
a count of the whole catalog.
detail is absent here, and that is the normal case. It is inlined only when
total is exactly 1 and the operation's request-body schema is small enough,
so that a search with one unambiguous answer saves a round trip. Two operations
matched, so pick one and run describe_operation on its id:
{ "operationId": "renew_domain_v1_domains__domain_reference__renew_post" }
which returns the operation's parameters, request-body schema and safety flags,
plus requiresConfirmation.
An operationId the catalog does not know is answered as a result, not an
error — so search again rather than retrying:
{
"status": "not_found",
"message": "unknown operationId: renew_domain_v2"
}
2. Call it
call_operation with:
{
"operationId": "renew_domain_v1_domains__domain_reference__renew_post",
"pathParams": { "domain_reference": "acme-labs.com" },
"body": {
"period": { "unit": "y", "value": 1 },
"current_expiry_date": "2026-11-04T00:00:00Z"
}
}
domain_reference accepts either a domain name or a domain ID. period and
current_expiry_date are both required — passing the expiry you believe is
current is what stops a renewal being applied twice.
3. Approve it
Renewal writes data and costs money, so this first call does not execute. It returns a confirmation challenge instead, and your client asks you to approve it. See Approvals and confirmations.
4. Read the result
After approval, the same call returns:
{
"status": "ok",
"operationId": "renew_domain_v1_domains__domain_reference__renew_post",
"httpStatus": 200,
"headers": {
"content-type": "application/json",
"x-server-request-id": "8f3c2e10-1b4a-4c6b-9e77-2a91f5d0c4aa"
},
"data": { "…the updated domain…" },
"truncated": false
}
status is "ok" for a 2xx or 3xx response and
"api_error" for 4xx and 5xx. An HTTP error from OpusDNS is a normal
tool result carrying httpStatus and the API's problem detail in
data — not a protocol failure. Quote
headers["x-server-request-id"] when you contact support.
Portfolio: read a lot of domains cheaply
portfolio_summary takes no arguments and answers "how big is this account, and
what is expiring":
{
"status": "ok",
"operationId": "get_domain_summary_v1_domains_summary_get",
"httpStatus": 200,
"data": {
"organization_id": "organization_01h45ytscbebyvny4gc8cr8ma2",
"domains": {
"total_count": 4127,
"by_status": { "ok": 4001, "pendingTransfer": 12, "clientTransferProhibited": 114 },
"by_tld": { "com": 2610, "de": 802, "io": 415 },
"expiring_soon": { "next_30_days": 412, "next_60_days": 733, "next_90_days": 1104 }
}
}
}
portfolio_query lists domains but returns only the fields you name, which is
what keeps a four-thousand-domain account out of the model's context:
{
"selector": { "tld": ["com"], "expires_in_30_days": true },
"fields": ["name", "domain_id", "expires_on", "renewal_mode"],
"pageSize": 50
}
{
"status": "ok",
"fields": ["name", "domain_id", "expires_on", "renewal_mode"],
"results": [
{
"name": "acme-labs.com",
"domain_id": "domain_01h45ytscbebyvny4gc8cr8ma2",
"expires_on": "2026-09-14T00:00:00Z",
"renewal_mode": "expire"
}
],
"pagination": {
"current_page": 1,
"page_size": 50,
"total_items": 412,
"total_pages": 9,
"has_next_page": true,
"has_previous_page": false
}
}
Omit fields and you get name, domain_id, expires_on, renewal_mode and
status_tags. pageSize is capped server-side at 200.
The selector
selector is the filter set of GET /v1/domains, and the same
object drives the bulk tools. The filters worth knowing:
| Filter | Type | Notes |
|---|---|---|
tag_ids |
list of tag IDs | IDs such as tag_01h45ytscbebyvny4gc8cr8ma2, not labels |
tag_mode |
match_any, match_all or match_none |
Defaults to match_any |
status_tags |
list | For example VERIFICATION_REQUIRED, INBOUND_TRANSFER_PENDING |
tld |
list | For example ["com", "org"] |
search, name, sld |
string | Match by full name, second-level label, or free text |
expires_in_30_days, expires_in_60_days, expires_in_90_days |
boolean | |
expires_before, expires_after |
RFC 3339 | Also created_*, updated_*, registered_*, transferred_* |
transfer_lock, is_premium |
boolean | |
registry_statuses |
list | |
include |
list | ["tags"] populates tags and status_tags, which are otherwise null |
tag_ids takes tag IDs, not labels. Ask for the tag
by name and the agent will look its ID up first. See
User tags.
An empty selector matches every domain in the account. That is harmless for a read, and consequential for a bulk submission.
Bulk: from a query to a change
Two calls — bulk_preview, then bulk_submit with the same arguments. The
selector resolves server-side, so the model never transcribes a domain name, and
the approval you give is bound to the exact set that was resolved.
The useful property is that the same selector means the same thing to both
families. Find the set with portfolio_query and read its
pagination.total_items; hand the identical selector to bulk_preview and read
matchedDomains. The two numbers agreeing is the check to make before you
approve — if they disagree, the selector is not describing what you think it is.
Full walkthrough: Bulk operations with Jobs. Task-shaped examples: Recipes.
Related
- Recipes — the same tools, arranged by task
- Results and errors — every shape a tool can return
- Tools reference — every parameter of every tool
-
Sub-organizations — the
organizationIdparameter every one of these tools takes - Renew a domain, Manage a domain
- API Reference

