core

Sherlock is a python SDK for AI agents to interact with the Sherlock API.

Sherlock

This is the main class for the SDK. If a private key is not provided, we will try to load it from the config file. If neither the private key nor the config file is provided, we will generate a new one and store it in the config file.


source

Sherlock


def Sherlock(
    priv:str='', # private key
):

Sherlock client class to interact with the Sherlock API.

s = Sherlock(priv)
s
Sherlock(pubkey=90ba884688884277e49080712f386eebc88806efa8345ca937f75fe80950156d)

Me

Let’s do an authenticated request to verify we’re authenticated.

Exported source
def _mk_headers(tok): return {"Authorization": f"Bearer {tok}"}
r = httpx.get(me_endpoint, headers=_mk_headers(s.atok))
r, r.json()
(<Response [200 OK]>,
 {'logged_in': True,
  'email': 'jordi@fewsats.com',
  'public_key': '90ba884688884277e49080712f386eebc88806efa8345ca937f75fe80950156d'})

source

Sherlock.me


def me(
    
):

Get authenticated user information

s.me()
{'logged_in': True,
 'email': 'jordi@fewsats.com',
 'public_key': '90ba884688884277e49080712f386eebc88806efa8345ca937f75fe80950156d'}

API methods

Claim account

Accounts created by AI Agents can link an email. After confirming the account users will be able log in and use the web interface for Sherlock Domains.

An email cannot be claimed more than once.


source

Sherlock.claim_account


def claim_account(
    email:str
):

Claim an account by linking an email address

Search domains

Search domains returns domain availability and its prices in USD cents.


source

Sherlock.search


def search(
    q:str, # query
):

Search for domains with a query. Returns prices in USD cents.

sr = s.search("trakwiska")
# Don't print the whole lists
sr['available'] = sr['available'][:1]
sr['unavailable'] = sr['unavailable'][:1]
sr
{'id': 'd1805b55-d448-4d1d-94ff-97e4e3e7642e',
 'created_at': '2025-03-18T08:58:56.382Z',
 'available': [{'name': 'trakwiska.net',
   'tld': 'net',
   'tags': [],
   'price': 1185,
   'currency': 'USD',
   'available': True}],
 'unavailable': []}

Contact Information

Contact information is required for ICANN domain registration and billing.


source

Contact


def Contact(
    first_name, last_name, email, address, city, state, postal_code, country
):

Contact information for a domain purchase


source

Sherlock.get_contact_information


def get_contact_information(
    
):

Get the contact information for the Sherlock user.


source

Sherlock.set_contact_information


def set_contact_information(
    cfn:str='', # contact first name
    cln:str='', # contact last name
    cem:str='', # contact email
    cadd:str='', # contact address
    cct:str='', # contact city
    cst:str='', # contact state
    cpc:str='', # contact postal code
    ccn:str='', # contact country
):

Set the contact information for the Sherlock user


source

Contact.is_valid


def is_valid(
    
):

Check if the contact information is valid

data = {
    "name": 'pol',
    "email": 'pol@sherlockdomains.com',
    "address": '123 Test St',
    "city": 'Test City',
    "state": 'CA',
    "postal_code": '12345',
    "country": 'US'
}
r = httpx.post(f"{API_URL}/api/v0/users/contact-information", json=data, headers=_mk_headers(s.atok))
r, r.text
(<Response [422 Unprocessable Content]>,
 '{"detail": [{"type": "missing", "loc": ["body", "data", "first_name"], "msg": "Field required"}, {"type": "missing", "loc": ["body", "data", "last_name"], "msg": "Field required"}]}')
info = {
    "first_name": "Test",
    "last_name": "User",
    "email": "test@example.com",
    "address": "123 Test St",
    "city": "Test City",
    "state": "CA",
    "country": "US",
    "postal_code": "12345",
}  

c = Contact(**info)
c, c.is_valid()
(Contact(first_name='Test', last_name='User', email='test@example.com', address='123 Test St', city='Test City', state='CA', postal_code='12345', country='US'),
 True)
r = s.set_contact_information(
    cfn=info['first_name'],
    cln=info['last_name'],
    cem=info['email'],
    cadd=info['address'],
    cct=info['city'],
    cst=info['state'],
    cpc=info['postal_code'],
    ccn=info['country']
)
r
{'message': 'Contact information updated successfully'}
r = s.get_contact_information()
r
{'first_name': 'Test',
 'last_name': 'User',
 'address': '123 Test St',
 'city': 'Test City',
 'state': 'CA',
 'postal_code': '12345',
 'country': 'US',
 'email': 'test@example.com'}

Purchase a domain

A purchase needs to be linked to a search id. The purchase flow implements the L402 protocol. The flow has two steps:

  1. Get available payment options for a domain
  2. Get payment details (checkout URL for credit card, invoice for Lightning Network) so it can be completed outband.
pd = _get_offers_payload("trakwiska.com", c, sr['id'])
pd
{'domain': 'trakwiska.com',
 'contact_information': {'first_name': 'Test',
  'last_name': 'User',
  'email': 'test@example.com',
  'address': '123 Test St',
  'city': 'Test City',
  'state': 'CA',
  'postal_code': '12345',
  'country': 'US'},
 'search_id': 'd1805b55-d448-4d1d-94ff-97e4e3e7642e'}
r = httpx.post(get_offers_endpoint, json=pd, headers=_mk_headers(s.atok))
r, r.json()
(<Response [402 Payment Required]>,
 {'version': '0.2.1',
  'payment_request_url': 'https://api.sherlockdomains.com/api/v0/payments/l402/payment_request',
  'payment_context_token': '90ba884688884277e49080712f386eebc88806efa8345ca937f75fe80950156d',
  'offers': [{'id': 'c377d60a-af98-48fd-a258-70c22de2d95c',
    'title': 'trakwiska.com',
    'description': 'Purchase trakwiska.com for 11.05 USD',
    'type': 'one-time',
    'amount': 1105,
    'currency': 'USD',
    'payment_methods': ['credit_card', 'lightning']}]})

source

Sherlock.get_purchase_offers


def get_purchase_offers(
    sid:str, # search id
    domain:str, # domain
    c:Contact, # contact information
):

Request available payment options for a domain.

Requesting a purchase will return a list of available offers and payment methods.

ofs = s.get_purchase_offers(sr['id'], "trakwiska.com")
ofs
{'version': '0.2.1',
 'payment_request_url': 'https://api.sherlockdomains.com/api/v0/payments/l402/payment_request',
 'payment_context_token': '90ba884688884277e49080712f386eebc88806efa8345ca937f75fe80950156d',
 'offers': [{'id': '533f89b7-67fa-48fe-9517-5ae32ce89d65',
   'title': 'trakwiska.com',
   'description': 'Purchase trakwiska.com for 11.05 USD',
   'type': 'one-time',
   'amount': 1105,
   'currency': 'USD',
   'payment_methods': ['credit_card', 'lightning']}]}

In order to pay for the domain you will have to request the payment details of the offer you want to pay for.

data = {
    "offer_id": first(ofs['offers'])['id'],
    "payment_method": 'credit_card',
    "payment_context_token": ofs['payment_context_token']
}
r = httpx.post(ofs['payment_request_url'], json=data)
r, r.json()
(<Response [200 OK]>,
 {'payment_method': {'checkout_url': 'https://checkout.stripe.com/c/pay/cs_live_a1Om85Efvv1lgfE8BbhUNibQyzwoEfG8qvKPeOVoSt5tv0wx2rxcgiKofV#fidkdWxOYHwnPyd1blppbHNgWjA0S3VzXDdBbTFNVlJzfDVRQVQ2dVdBTnJTSH1QMGs2dHRsanJMbkY0PTxKbUtRaWowT2NwMGM8RlVBbGRqSWo3UFYwcVdqR3F9N2BtM2ZTPXc1Z3dQXGc2NTVPYVVSQkM8bycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl',
   'lightning_invoice': None},
  'expires_at': '2025-01-14T04:04:35.484Z'})

source

Sherlock.get_payment_details


def get_payment_details(
    prurl:str, # payment request url
    oid:str, # offer id
    pm:str, # payment method
    pct:str, # payment context token
):

Get payment details for an offer.


source

Sherlock.request_payment_details


def request_payment_details(
    sid:str, # search id
    domain:str, # domain
    payment_method:str='credit_card', # payment method {'credit_card', 'lightning'}
    contact:Contact=None, # contact information
):

Request payment information for purchasing a domain. Returns the details needed to complete the payment (like a checkout URL).

X402 Purchase

The X402 purchase flow uses the X402 protocol (USDC on Base blockchain via Coinbase CDP). The flow has two steps:

  1. Get X402 payment requirements for a domain (returns 402 with PaymentRequired schema)
  2. Complete the purchase by providing a PAYMENT-SIGNATURE header (produced externally, e.g. via the x402 Python library)

source

Sherlock.get_x402_purchase_offers


def get_x402_purchase_offers(
    sid:str, # search id
    domain:str, # domain
    c:Contact, # contact information
):

Request X402 payment requirements for a domain purchase.


source

Sherlock.purchase_x402


def purchase_x402(
    sid:str, # search id
    domain:str, # domain
    payment_signature:str, # PAYMENT-SIGNATURE header value
    c:Contact, # contact information
):

Complete an X402 domain purchase with a payment signature.

DNS methods


source

Sherlock.domains


def domains(
    
):

List of domains owned by the authenticated user

ds = s.domains()
ds
[{'id': 'd9b2cc30-c15d-44b9-9d39-5d33da504484',
  'domain_name': 'h402.org',
  'created_at': '2024-12-28T18:58:49.899Z',
  'expires_at': '2025-05-11T00:00:00Z',
  'auto_renew': False,
  'locked': True,
  'private': True,
  'nameservers': ['paislee.ns.cloudflare.com', 'trevor.ns.cloudflare.com'],
  'status': 'active'},
 {'id': 'ed6fa317-81dd-42ec-91b1-7b4702e1e5ab',
  'domain_name': 'l402.org',
  'created_at': '2025-02-04T06:35:03.956Z',
  'expires_at': '2025-05-03T00:00:00Z',
  'auto_renew': True,
  'locked': True,
  'private': True,
  'nameservers': [],
  'status': 'active'},
 {'id': '203e13d0-d5cd-477e-a6b8-199f2d6264aa',
  'domain_name': 'smartcheckout.dev',
  'created_at': '2025-06-24T04:40:19.830Z',
  'expires_at': '2025-06-24T04:54:45.281Z',
  'auto_renew': False,
  'locked': True,
  'private': True,
  'nameservers': [],
  'status': 'active'}]

source

Sherlock.update_nameservers


def update_nameservers(
    domain_id:str, # domain id
    nameservers:list, # nameservers
):

Update the nameserver list for a domain

did = first(ds)['id']
rs = s.update_nameservers(did, ['ns1.example.com', 'ns2.example.com'])
rs
{'domains': ['h402.org'], 'success': True}

source

Sherlock.dns_records


def dns_records(
    domain_id:str, # domain id
):

Get DNS records for a domain.

did = first(ds)['id']
rs = s.dns_records(did)
rs
{'domain': 'h402.org',
 'records': [{'id': '8c1df0e3ad7ff4b30695a11e20d84b72',
   'type': 'A',
   'name': 'h402.org',
   'value': '76.76.21.21',
   'ttl': 3600}]}

source

Sherlock.create_dns


def create_dns(
    domain_id:str, # domain id
    type:str='TXT', # type
    name:str='test', # name
    value:str='test-1', # value
    ttl:int=3600, # ttl
):

Create a new DNS record

entry = s.create_dns(
    domain_id=did,
    type="TXT",
    name="test-sherlock",  # This will create test-sherlock.yourdomain.com
    value="hello-world",   # The actual text content
    ttl=3600              # Time to live in seconds
)

created_record_id = first(entry['records'])['id']
created_record_id, entry
('b22820c45b6f2a48461c3a52ca486b5a',
 {'domain': 'h402.org',
  'records': [{'id': 'b22820c45b6f2a48461c3a52ca486b5a',
    'type': 'TXT',
    'name': 'test-sherlock',
    'value': 'hello-world',
    'ttl': 3600}]})
s.dns_records(did)
{'domain': 'h402.org',
 'records': [{'id': '8c1df0e3ad7ff4b30695a11e20d84b72',
   'type': 'A',
   'name': 'h402.org',
   'value': '76.76.21.21',
   'ttl': 3600},
  {'id': 'b22820c45b6f2a48461c3a52ca486b5a',
   'type': 'TXT',
   'name': 'test-sherlock.h402.org',
   'value': 'hello-world',
   'ttl': 3600}]}

source

Sherlock.update_dns


def update_dns(
    domain_id:str, # domain id
    record_id:str, # record id
    type:str='TXT', # type
    name:str='test-2', # name
    value:str='test-2', # value
    ttl:int=3600, # ttl
):

Update a DNS record

updated_record = s.update_dns(
    domain_id=did,
    record_id=entry['records'][0]['id'],
    type="TXT",
    name="test-sherlock",
    value="hello-world-updated",
    ttl=3600
)
updated_record_id = first(updated_record['records'])['id']
updated_record_id, updated_record
('3944584c93667d49c774e7823a039cd8',
 {'domain': 'h402.org',
  'records': [{'id': '3944584c93667d49c774e7823a039cd8',
    'type': 'TXT',
    'name': 'test-sherlock',
    'value': 'hello-world-updated',
    'ttl': 3600}]})

source

Sherlock.delete_dns


def delete_dns(
    domain_id:str, # domain id
    record_id:str, # record id
):

Delete a DNS record

s.delete_dns(did, updated_record_id)
{'domain': 'h402.org', 'deleted_records': ['3944584c93667d49c774e7823a039cd8']}

We expose Sherlock’s core functionality as tools for AI agents. Note that payment handling for L402 offers requires additional tools like fewsats.Client().pay.


source

Sherlock.as_tools


def as_tools(
    
):

Return the Sherlock class as a list of tools ready for agents to use

s.as_tools().map(lambda t: t.__name__)
(#10) ['_me','_set_contact_information','_get_contact_information','_search','_purchase_domain','_domains','_dns_records','_create_dns_record','_update_dns_record','_delete_dns_record']

CLI


source

Sherlock.as_cli


def as_cli(
    
):

Return the Sherlock class as a list of tools ready for agents to use

You can use the Sherlock class as a CLI tool.

 sherlock
usage: sherlock [-h] {me,set_contact_information,get_contact_information,search,purchase_domain,domains,dns_records,create_dns,update_dns,delete_dns} ...

positional arguments:
  {me,set_contact_information,get_contact_information,search,purchase_domain,domains,dns_records,create_dns,update_dns,delete_dns}
    me                  Get authenticated user information
    set_contact_information
                        Set the contact information for the Sherlock user
    get_contact_information
                        Get the contact information for the Sherlock user.
    search              Search for domains with a query. Returns prices in USD cents.
    purchase_domain     Request payment information for purchasing a domain. Returns the details needed to complete the payment (like a checkout URL).
    domains             List of domains owned by the authenticated user
    dns_records         Get DNS records for a domain.
    create_dns          Create a new DNS record
    update_dns          Update a DNS record
    delete_dns          Delete a DNS record

options:
  -h, --help            show this help message and exit

source

main


def main(
    
):

CLI interface for Sherlock