domainkits 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- domainkits-0.1.0/LICENSE +21 -0
- domainkits-0.1.0/PKG-INFO +173 -0
- domainkits-0.1.0/README.md +149 -0
- domainkits-0.1.0/domainkits/__init__.py +123 -0
- domainkits-0.1.0/domainkits/client.py +181 -0
- domainkits-0.1.0/domainkits.egg-info/PKG-INFO +173 -0
- domainkits-0.1.0/domainkits.egg-info/SOURCES.txt +9 -0
- domainkits-0.1.0/domainkits.egg-info/dependency_links.txt +1 -0
- domainkits-0.1.0/domainkits.egg-info/top_level.txt +1 -0
- domainkits-0.1.0/pyproject.toml +37 -0
- domainkits-0.1.0/setup.cfg +4 -0
domainkits-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DomainKits
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: domainkits
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the DomainKits REST API. Search expiring, newly registered, aged, active, deleted and marketplace domains, plus WHOIS, DNS, Certificate Transparency and trends.
|
|
5
|
+
Author-email: DomainKits <info@domainkits.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://domainkits.com/dev
|
|
8
|
+
Project-URL: Documentation, https://domainkits.com/dev/api-docs
|
|
9
|
+
Project-URL: Repository, https://github.com/ABTdomain/domainkits-sdk-py
|
|
10
|
+
Keywords: domain,domain-search,expired-domains,newly-registered-domains,whois,dns,certificate-transparency,domainkits
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# domainkits
|
|
26
|
+
|
|
27
|
+
Python client for the [DomainKits](https://domainkits.com) REST API.
|
|
28
|
+
|
|
29
|
+
DomainKits is one API with a shared key across every endpoint. This package covers all of them, six domain search types, WHOIS, DNS, reverse nameserver, Certificate Transparency, safety, trends and bulk download, with automatic paging and structured quota errors. No dependencies.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
The REST API is for Premium and Platinum accounts; unauthenticated requests are rejected with 401. Keys start with `dk_` and come from [domainkits.com](https://domainkits.com/pricing).
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install domainkits
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import os
|
|
45
|
+
from domainkits import DomainKits
|
|
46
|
+
|
|
47
|
+
dk = DomainKits(os.environ["DOMAINKITS_API_KEY"])
|
|
48
|
+
|
|
49
|
+
result = dk.expired.list(
|
|
50
|
+
keyword="clinic",
|
|
51
|
+
tld="com",
|
|
52
|
+
length="5-10",
|
|
53
|
+
no_number=True,
|
|
54
|
+
no_hyphen=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
print(f"{result['total']} matches")
|
|
58
|
+
for d in result["data"]:
|
|
59
|
+
print(d["domain"], d["age"], d["status"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Paging
|
|
63
|
+
|
|
64
|
+
A single request returns at most 500 results. `paginate` walks the whole result set for you:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
for domain in dk.expired.paginate(keyword="clinic", tld="com"):
|
|
68
|
+
print(domain["domain"])
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It stops when the result set is exhausted. Break out of the loop whenever you have enough; no further requests are made.
|
|
72
|
+
|
|
73
|
+
### Export
|
|
74
|
+
|
|
75
|
+
`export` pulls up to 50,000 rows as CSV in one request:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
csv = dk.expired.export(tld="com", status="pending_delete")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This runs on a separate, much smaller quota, 10 per day and 100 per month on Premium, 3 and 9 during the trial. It is for occasional bulk pulls, not for a scheduled job. The export also returns fewer columns than paged mode: `registered_date` is the year only, and `age` is omitted.
|
|
82
|
+
|
|
83
|
+
50,000 is a cap, not a promise of completeness. Browsing `.com` matched 4,847,613 expiring domains on 27 July 2026, so an unfiltered export returns the first 50,000. Narrow the query if you need the result set to fit.
|
|
84
|
+
|
|
85
|
+
## Search types
|
|
86
|
+
|
|
87
|
+
| Attribute | What it searches |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `dk.expired` | Domains in the deletion cycle: expired, redemption, pending delete |
|
|
90
|
+
| `dk.nrds` | Newly registered domains, last 60 days |
|
|
91
|
+
| `dk.aged` | Domains with 5 to 20+ years of registration history |
|
|
92
|
+
| `dk.active` | Currently registered domains |
|
|
93
|
+
| `dk.deleted` | Dropped domains (requires `keyword`) |
|
|
94
|
+
| `dk.market` | Domains listed for sale on marketplaces |
|
|
95
|
+
|
|
96
|
+
Each has `list`, `paginate` and `export`. Filters are passed as keyword arguments and match the REST parameter names: an expired result carries `status`, an NRD result carries `expiry_date`, a market result carries `marketplace`.
|
|
97
|
+
|
|
98
|
+
## Other endpoints
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
dk.whois("example.com")
|
|
102
|
+
dk.dns("example.com")
|
|
103
|
+
dk.safety("example.com")
|
|
104
|
+
dk.ns_reverse("ns1.example.com", tld="com")
|
|
105
|
+
dk.tld_check("yourbrand")
|
|
106
|
+
dk.typosquat("example.com")
|
|
107
|
+
dk.ip_lookup("8.8.8.8")
|
|
108
|
+
dk.registrar("godaddy")
|
|
109
|
+
dk.status_guide("clientHold")
|
|
110
|
+
dk.monitor_changes(tld="com", reason="transfer")
|
|
111
|
+
dk.ct_subdomains("example.com")
|
|
112
|
+
dk.ct_certs(domain="example.com")
|
|
113
|
+
dk.ct_search("example")
|
|
114
|
+
dk.tld_trends("newly", tld="com")
|
|
115
|
+
dk.keyword_trends("hot")
|
|
116
|
+
dk.usage()
|
|
117
|
+
dk.search_status()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Coverage
|
|
121
|
+
|
|
122
|
+
**gTLDs only** for the domain search endpoints. The index covers generic TLDs: `.com`, `.net`, `.org`, `.info`, `.biz`, `.xyz`, `.online`, `.site`, `.top`, `.club`, `.live`, `.app`, `.dev` and others. Country-code TLDs are not indexed: a query for `.de`, `.io`, `.co` or `.us` returns an empty result set, not an error.
|
|
123
|
+
|
|
124
|
+
`whois`, `dns`, `safety`, `ip_lookup` and the Certificate Transparency endpoints work on any domain, ccTLDs included.
|
|
125
|
+
|
|
126
|
+
**No PII.** Responses contain no personal data. WHOIS results are limited to registrar, dates, status codes and nameservers; registrant names, emails, addresses and phone numbers are not returned.
|
|
127
|
+
|
|
128
|
+
## Errors
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from domainkits import DomainKits, DomainKitsError, RateLimitError, AuthError
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
dk.expired.export(tld="com")
|
|
135
|
+
except RateLimitError as error:
|
|
136
|
+
print(f"Quota exhausted. Retry in {error.retry_after_ms}ms")
|
|
137
|
+
print(error.rate_limit)
|
|
138
|
+
except AuthError:
|
|
139
|
+
print("Key rejected, check your plan tier")
|
|
140
|
+
except DomainKitsError as error:
|
|
141
|
+
print(error.status, error)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every error carries the `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` values as a parsed `rate_limit` object. `RateLimitError.retry_after_ms` tells you how long until the window resets.
|
|
145
|
+
|
|
146
|
+
429 and 5xx responses are retried automatically, twice by default, waiting until the rate-limit window resets when that is under two minutes. Set `max_retries=0` to handle it yourself.
|
|
147
|
+
|
|
148
|
+
## Options
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
dk = DomainKits(
|
|
152
|
+
api_key=os.environ["DOMAINKITS_API_KEY"],
|
|
153
|
+
timeout=60.0,
|
|
154
|
+
max_retries=2,
|
|
155
|
+
base_url="https://premium-api.domainkits.com/api/v1",
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Quotas
|
|
160
|
+
|
|
161
|
+
Call `usage()` for the live picture on your account; every endpoint reports its own per-minute, daily and monthly allowance alongside what you have already spent.
|
|
162
|
+
|
|
163
|
+
Daily quotas reset at 00:00 UTC, monthly quotas on the 1st. Current limits: [domainkits.com/dev/api-docs](https://domainkits.com/dev/api-docs).
|
|
164
|
+
|
|
165
|
+
## Resources
|
|
166
|
+
|
|
167
|
+
- [DomainKits API reference](https://domainkits.com/dev/api-docs)
|
|
168
|
+
- [@domainkits/sdk](https://www.npmjs.com/package/@domainkits/sdk), the same API for TypeScript
|
|
169
|
+
- [n8n-nodes-domainkits](https://www.npmjs.com/package/n8n-nodes-domainkits), the same API for n8n
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# domainkits
|
|
2
|
+
|
|
3
|
+
Python client for the [DomainKits](https://domainkits.com) REST API.
|
|
4
|
+
|
|
5
|
+
DomainKits is one API with a shared key across every endpoint. This package covers all of them, six domain search types, WHOIS, DNS, reverse nameserver, Certificate Transparency, safety, trends and bulk download, with automatic paging and structured quota errors. No dependencies.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
The REST API is for Premium and Platinum accounts; unauthenticated requests are rejected with 401. Keys start with `dk_` and come from [domainkits.com](https://domainkits.com/pricing).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install domainkits
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
from domainkits import DomainKits
|
|
22
|
+
|
|
23
|
+
dk = DomainKits(os.environ["DOMAINKITS_API_KEY"])
|
|
24
|
+
|
|
25
|
+
result = dk.expired.list(
|
|
26
|
+
keyword="clinic",
|
|
27
|
+
tld="com",
|
|
28
|
+
length="5-10",
|
|
29
|
+
no_number=True,
|
|
30
|
+
no_hyphen=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
print(f"{result['total']} matches")
|
|
34
|
+
for d in result["data"]:
|
|
35
|
+
print(d["domain"], d["age"], d["status"])
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Paging
|
|
39
|
+
|
|
40
|
+
A single request returns at most 500 results. `paginate` walks the whole result set for you:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
for domain in dk.expired.paginate(keyword="clinic", tld="com"):
|
|
44
|
+
print(domain["domain"])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It stops when the result set is exhausted. Break out of the loop whenever you have enough; no further requests are made.
|
|
48
|
+
|
|
49
|
+
### Export
|
|
50
|
+
|
|
51
|
+
`export` pulls up to 50,000 rows as CSV in one request:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
csv = dk.expired.export(tld="com", status="pending_delete")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This runs on a separate, much smaller quota, 10 per day and 100 per month on Premium, 3 and 9 during the trial. It is for occasional bulk pulls, not for a scheduled job. The export also returns fewer columns than paged mode: `registered_date` is the year only, and `age` is omitted.
|
|
58
|
+
|
|
59
|
+
50,000 is a cap, not a promise of completeness. Browsing `.com` matched 4,847,613 expiring domains on 27 July 2026, so an unfiltered export returns the first 50,000. Narrow the query if you need the result set to fit.
|
|
60
|
+
|
|
61
|
+
## Search types
|
|
62
|
+
|
|
63
|
+
| Attribute | What it searches |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `dk.expired` | Domains in the deletion cycle: expired, redemption, pending delete |
|
|
66
|
+
| `dk.nrds` | Newly registered domains, last 60 days |
|
|
67
|
+
| `dk.aged` | Domains with 5 to 20+ years of registration history |
|
|
68
|
+
| `dk.active` | Currently registered domains |
|
|
69
|
+
| `dk.deleted` | Dropped domains (requires `keyword`) |
|
|
70
|
+
| `dk.market` | Domains listed for sale on marketplaces |
|
|
71
|
+
|
|
72
|
+
Each has `list`, `paginate` and `export`. Filters are passed as keyword arguments and match the REST parameter names: an expired result carries `status`, an NRD result carries `expiry_date`, a market result carries `marketplace`.
|
|
73
|
+
|
|
74
|
+
## Other endpoints
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
dk.whois("example.com")
|
|
78
|
+
dk.dns("example.com")
|
|
79
|
+
dk.safety("example.com")
|
|
80
|
+
dk.ns_reverse("ns1.example.com", tld="com")
|
|
81
|
+
dk.tld_check("yourbrand")
|
|
82
|
+
dk.typosquat("example.com")
|
|
83
|
+
dk.ip_lookup("8.8.8.8")
|
|
84
|
+
dk.registrar("godaddy")
|
|
85
|
+
dk.status_guide("clientHold")
|
|
86
|
+
dk.monitor_changes(tld="com", reason="transfer")
|
|
87
|
+
dk.ct_subdomains("example.com")
|
|
88
|
+
dk.ct_certs(domain="example.com")
|
|
89
|
+
dk.ct_search("example")
|
|
90
|
+
dk.tld_trends("newly", tld="com")
|
|
91
|
+
dk.keyword_trends("hot")
|
|
92
|
+
dk.usage()
|
|
93
|
+
dk.search_status()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Coverage
|
|
97
|
+
|
|
98
|
+
**gTLDs only** for the domain search endpoints. The index covers generic TLDs: `.com`, `.net`, `.org`, `.info`, `.biz`, `.xyz`, `.online`, `.site`, `.top`, `.club`, `.live`, `.app`, `.dev` and others. Country-code TLDs are not indexed: a query for `.de`, `.io`, `.co` or `.us` returns an empty result set, not an error.
|
|
99
|
+
|
|
100
|
+
`whois`, `dns`, `safety`, `ip_lookup` and the Certificate Transparency endpoints work on any domain, ccTLDs included.
|
|
101
|
+
|
|
102
|
+
**No PII.** Responses contain no personal data. WHOIS results are limited to registrar, dates, status codes and nameservers; registrant names, emails, addresses and phone numbers are not returned.
|
|
103
|
+
|
|
104
|
+
## Errors
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from domainkits import DomainKits, DomainKitsError, RateLimitError, AuthError
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
dk.expired.export(tld="com")
|
|
111
|
+
except RateLimitError as error:
|
|
112
|
+
print(f"Quota exhausted. Retry in {error.retry_after_ms}ms")
|
|
113
|
+
print(error.rate_limit)
|
|
114
|
+
except AuthError:
|
|
115
|
+
print("Key rejected, check your plan tier")
|
|
116
|
+
except DomainKitsError as error:
|
|
117
|
+
print(error.status, error)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Every error carries the `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` values as a parsed `rate_limit` object. `RateLimitError.retry_after_ms` tells you how long until the window resets.
|
|
121
|
+
|
|
122
|
+
429 and 5xx responses are retried automatically, twice by default, waiting until the rate-limit window resets when that is under two minutes. Set `max_retries=0` to handle it yourself.
|
|
123
|
+
|
|
124
|
+
## Options
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
dk = DomainKits(
|
|
128
|
+
api_key=os.environ["DOMAINKITS_API_KEY"],
|
|
129
|
+
timeout=60.0,
|
|
130
|
+
max_retries=2,
|
|
131
|
+
base_url="https://premium-api.domainkits.com/api/v1",
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Quotas
|
|
136
|
+
|
|
137
|
+
Call `usage()` for the live picture on your account; every endpoint reports its own per-minute, daily and monthly allowance alongside what you have already spent.
|
|
138
|
+
|
|
139
|
+
Daily quotas reset at 00:00 UTC, monthly quotas on the 1st. Current limits: [domainkits.com/dev/api-docs](https://domainkits.com/dev/api-docs).
|
|
140
|
+
|
|
141
|
+
## Resources
|
|
142
|
+
|
|
143
|
+
- [DomainKits API reference](https://domainkits.com/dev/api-docs)
|
|
144
|
+
- [@domainkits/sdk](https://www.npmjs.com/package/@domainkits/sdk), the same API for TypeScript
|
|
145
|
+
- [n8n-nodes-domainkits](https://www.npmjs.com/package/n8n-nodes-domainkits), the same API for n8n
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterator
|
|
4
|
+
|
|
5
|
+
from .client import (
|
|
6
|
+
AuthError,
|
|
7
|
+
DomainKitsClient,
|
|
8
|
+
DomainKitsError,
|
|
9
|
+
RateLimit,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
14
|
+
__all__ = [
|
|
15
|
+
"DomainKits",
|
|
16
|
+
"DomainKitsError",
|
|
17
|
+
"RateLimitError",
|
|
18
|
+
"AuthError",
|
|
19
|
+
"RateLimit",
|
|
20
|
+
"SearchResource",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
MAX_LIMIT = 500
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SearchResource:
|
|
27
|
+
def __init__(self, client: DomainKitsClient, path: str):
|
|
28
|
+
self._client = client
|
|
29
|
+
self._path = path
|
|
30
|
+
|
|
31
|
+
def list(self, **params: Any) -> dict[str, Any]:
|
|
32
|
+
return self._client.request_list(self._path, params)
|
|
33
|
+
|
|
34
|
+
def paginate(self, page_size: int = MAX_LIMIT, **params: Any) -> Iterator[dict[str, Any]]:
|
|
35
|
+
offset = int(params.pop("offset", 0) or 0)
|
|
36
|
+
limit = min(page_size, MAX_LIMIT)
|
|
37
|
+
while True:
|
|
38
|
+
result = self._client.request_list(self._path, {**params, "limit": limit, "offset": offset})
|
|
39
|
+
data = result["data"]
|
|
40
|
+
yield from data
|
|
41
|
+
offset += len(data)
|
|
42
|
+
if not data or len(data) < limit or offset >= result["total"]:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
def export(self, **params: Any) -> str:
|
|
46
|
+
_, _, body = self._client.request_raw(self._path, {**params, "export": "csv"})
|
|
47
|
+
return body.decode("utf-8")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DomainKits:
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
api_key: str,
|
|
54
|
+
base_url: str | None = None,
|
|
55
|
+
timeout: float = 60.0,
|
|
56
|
+
max_retries: int = 2,
|
|
57
|
+
):
|
|
58
|
+
self._client = DomainKitsClient(api_key, base_url=base_url, timeout=timeout, max_retries=max_retries)
|
|
59
|
+
|
|
60
|
+
self.expired = SearchResource(self._client, "/search/expired")
|
|
61
|
+
self.nrds = SearchResource(self._client, "/search/nrds")
|
|
62
|
+
self.aged = SearchResource(self._client, "/search/aged")
|
|
63
|
+
self.active = SearchResource(self._client, "/search/active")
|
|
64
|
+
self.deleted = SearchResource(self._client, "/search/deleted")
|
|
65
|
+
self.market = SearchResource(self._client, "/search/market")
|
|
66
|
+
|
|
67
|
+
def usage(self) -> dict[str, Any]:
|
|
68
|
+
return self._client.request("/usage")
|
|
69
|
+
|
|
70
|
+
def search_status(self) -> dict[str, Any]:
|
|
71
|
+
return self._client.request("/search/status")
|
|
72
|
+
|
|
73
|
+
def health(self) -> dict[str, Any]:
|
|
74
|
+
return self._client.request_envelope("/health")
|
|
75
|
+
|
|
76
|
+
def whois(self, domain: str) -> dict[str, Any]:
|
|
77
|
+
return self._client.request("/whois", {"domain": domain})
|
|
78
|
+
|
|
79
|
+
def dns(self, domain: str) -> dict[str, Any]:
|
|
80
|
+
return self._client.request("/dns", {"domain": domain})
|
|
81
|
+
|
|
82
|
+
def safety(self, domain: str) -> dict[str, Any]:
|
|
83
|
+
return self._client.request("/safety", {"domain": domain})
|
|
84
|
+
|
|
85
|
+
def ip_lookup(self, query: str) -> dict[str, Any]:
|
|
86
|
+
return self._client.request("/ip-lookup", {"query": query})
|
|
87
|
+
|
|
88
|
+
def registrar(self, query: str) -> dict[str, Any]:
|
|
89
|
+
return self._client.request("/registrar", {"query": query})
|
|
90
|
+
|
|
91
|
+
def status_guide(self, query: str | None = None) -> dict[str, Any]:
|
|
92
|
+
return self._client.request("/status-guide", {"query": query})
|
|
93
|
+
|
|
94
|
+
def tld_check(self, prefix: str, **params: Any) -> dict[str, Any]:
|
|
95
|
+
return self._client.request_envelope("/tld-check", {"prefix": prefix, **params})
|
|
96
|
+
|
|
97
|
+
def ns_reverse(self, ns: str, **params: Any) -> dict[str, Any]:
|
|
98
|
+
return self._client.request_list("/ns-reverse", {"ns": ns, **params})
|
|
99
|
+
|
|
100
|
+
def typosquat(self, domain: str, **params: Any) -> dict[str, Any]:
|
|
101
|
+
return self._client.request_envelope("/typosquat", {"domain": domain, **params})
|
|
102
|
+
|
|
103
|
+
def monitor_changes(self, **params: Any) -> dict[str, Any]:
|
|
104
|
+
return self._client.request_list("/monitor/changes", params)
|
|
105
|
+
|
|
106
|
+
def ct_subdomains(self, domain: str, **params: Any) -> dict[str, Any]:
|
|
107
|
+
return self._client.request_list("/ct/subdomains", {"domain": domain, **params})
|
|
108
|
+
|
|
109
|
+
def ct_certs(self, **params: Any) -> dict[str, Any]:
|
|
110
|
+
return self._client.request_list("/ct/certs", params)
|
|
111
|
+
|
|
112
|
+
def ct_search(self, keyword: str, **params: Any) -> dict[str, Any]:
|
|
113
|
+
return self._client.request_list("/ct/search", {"keyword": keyword, **params})
|
|
114
|
+
|
|
115
|
+
def tld_trends(self, type: str, **params: Any) -> Any:
|
|
116
|
+
return self._client.request(f"/trends/tlds/{type}", params)
|
|
117
|
+
|
|
118
|
+
def keyword_trends(self, type: str, **params: Any) -> Any:
|
|
119
|
+
return self._client.request(f"/trends/keywords/{type}", params)
|
|
120
|
+
|
|
121
|
+
def nrds_download(self, **params: Any) -> bytes:
|
|
122
|
+
_, _, body = self._client.request_raw("/nrds/download", params)
|
|
123
|
+
return body
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import urllib.error
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import urllib.request
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
DEFAULT_BASE_URL = "https://premium-api.domainkits.com/api/v1"
|
|
13
|
+
DEFAULT_TIMEOUT = 60.0
|
|
14
|
+
DEFAULT_MAX_RETRIES = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class RateLimit:
|
|
19
|
+
limit: int | None
|
|
20
|
+
remaining: int | None
|
|
21
|
+
reset_at: datetime | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DomainKitsError(Exception):
|
|
25
|
+
def __init__(self, message: str, status: int, rate_limit: RateLimit):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.status = status
|
|
28
|
+
self.rate_limit = rate_limit
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RateLimitError(DomainKitsError):
|
|
32
|
+
def __init__(self, message: str, rate_limit: RateLimit):
|
|
33
|
+
super().__init__(message, 429, rate_limit)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def retry_after_ms(self) -> int | None:
|
|
37
|
+
if not self.rate_limit.reset_at:
|
|
38
|
+
return None
|
|
39
|
+
delta = (self.rate_limit.reset_at - datetime.now(timezone.utc)).total_seconds() * 1000
|
|
40
|
+
return max(0, int(delta))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AuthError(DomainKitsError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _read_rate_limit(headers: Any) -> RateLimit:
|
|
48
|
+
def num(name: str) -> int | None:
|
|
49
|
+
raw = headers.get(name)
|
|
50
|
+
if raw is None:
|
|
51
|
+
return None
|
|
52
|
+
try:
|
|
53
|
+
return int(raw)
|
|
54
|
+
except ValueError:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
reset = num("x-ratelimit-reset")
|
|
58
|
+
return RateLimit(
|
|
59
|
+
limit=num("x-ratelimit-limit"),
|
|
60
|
+
remaining=num("x-ratelimit-remaining"),
|
|
61
|
+
reset_at=datetime.fromtimestamp(reset, tz=timezone.utc) if reset is not None else None,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _extract_error(body: bytes) -> str | None:
|
|
66
|
+
try:
|
|
67
|
+
parsed = json.loads(body)
|
|
68
|
+
except (ValueError, UnicodeDecodeError):
|
|
69
|
+
return None
|
|
70
|
+
error = parsed.get("error") if isinstance(parsed, dict) else None
|
|
71
|
+
return error if isinstance(error, str) else None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _backoff_seconds(error: RateLimitError, attempt: int) -> float | None:
|
|
75
|
+
until_ms = error.retry_after_ms
|
|
76
|
+
if until_ms is None:
|
|
77
|
+
return float(2**attempt)
|
|
78
|
+
if until_ms > 120_000:
|
|
79
|
+
return None
|
|
80
|
+
return until_ms / 1000 + 0.25
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DomainKitsClient:
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
api_key: str,
|
|
87
|
+
base_url: str | None = None,
|
|
88
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
89
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
90
|
+
):
|
|
91
|
+
if not api_key:
|
|
92
|
+
raise ValueError("api_key is required. The DomainKits REST API rejects unauthenticated requests.")
|
|
93
|
+
self.api_key = api_key
|
|
94
|
+
self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
95
|
+
self.timeout = timeout
|
|
96
|
+
self.max_retries = max_retries
|
|
97
|
+
|
|
98
|
+
def request_raw(self, path: str, params: dict[str, Any] | None = None) -> tuple[int, Any, bytes]:
|
|
99
|
+
query: dict[str, str] = {}
|
|
100
|
+
for key, value in (params or {}).items():
|
|
101
|
+
if value is None or value == "":
|
|
102
|
+
continue
|
|
103
|
+
if isinstance(value, bool):
|
|
104
|
+
query[key] = "true" if value else "false"
|
|
105
|
+
elif isinstance(value, (list, tuple)):
|
|
106
|
+
query[key] = ",".join(str(v) for v in value)
|
|
107
|
+
else:
|
|
108
|
+
query[key] = str(value)
|
|
109
|
+
url = self.base_url + path
|
|
110
|
+
if query:
|
|
111
|
+
url += "?" + urllib.parse.urlencode(query)
|
|
112
|
+
|
|
113
|
+
last_error: DomainKitsError | None = None
|
|
114
|
+
for attempt in range(self.max_retries + 1):
|
|
115
|
+
request = urllib.request.Request(
|
|
116
|
+
url,
|
|
117
|
+
headers={
|
|
118
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
119
|
+
"Accept": "application/json",
|
|
120
|
+
"User-Agent": "domainkits-python/0.1.0",
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
response = urllib.request.urlopen(request, timeout=self.timeout)
|
|
125
|
+
return response.status, response.headers, response.read()
|
|
126
|
+
except urllib.error.HTTPError as e:
|
|
127
|
+
body = e.read()
|
|
128
|
+
rate_limit = _read_rate_limit(e.headers)
|
|
129
|
+
message = _extract_error(body) or f"Request failed with status {e.code}"
|
|
130
|
+
|
|
131
|
+
if e.code in (401, 403):
|
|
132
|
+
raise AuthError(message, e.code, rate_limit) from None
|
|
133
|
+
|
|
134
|
+
if e.code == 429:
|
|
135
|
+
rate_error = RateLimitError(message, rate_limit)
|
|
136
|
+
last_error = rate_error
|
|
137
|
+
wait = _backoff_seconds(rate_error, attempt)
|
|
138
|
+
if attempt < self.max_retries and wait is not None:
|
|
139
|
+
time.sleep(wait)
|
|
140
|
+
continue
|
|
141
|
+
raise rate_error from None
|
|
142
|
+
|
|
143
|
+
if e.code >= 500 and attempt < self.max_retries:
|
|
144
|
+
last_error = DomainKitsError(message, e.code, rate_limit)
|
|
145
|
+
time.sleep(0.5 * (2**attempt))
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
raise DomainKitsError(message, e.code, rate_limit) from None
|
|
149
|
+
|
|
150
|
+
raise last_error if last_error else DomainKitsError(
|
|
151
|
+
"Request failed after retries", 0, RateLimit(None, None, None)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _parse(self, path: str, params: dict[str, Any] | None) -> tuple[Any, int, Any]:
|
|
155
|
+
status, headers, body = self.request_raw(path, params)
|
|
156
|
+
envelope = json.loads(body)
|
|
157
|
+
if isinstance(envelope, dict) and envelope.get("success") is False:
|
|
158
|
+
raise DomainKitsError(
|
|
159
|
+
envelope.get("error") or "DomainKits API returned an error",
|
|
160
|
+
status,
|
|
161
|
+
_read_rate_limit(headers),
|
|
162
|
+
)
|
|
163
|
+
return envelope, status, headers
|
|
164
|
+
|
|
165
|
+
def request(self, path: str, params: dict[str, Any] | None = None) -> Any:
|
|
166
|
+
envelope, _, _ = self._parse(path, params)
|
|
167
|
+
if isinstance(envelope, dict) and envelope.get("data") is not None:
|
|
168
|
+
return envelope["data"]
|
|
169
|
+
return envelope
|
|
170
|
+
|
|
171
|
+
def request_list(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
172
|
+
envelope, _, _ = self._parse(path, params)
|
|
173
|
+
data = envelope.get("data") if isinstance(envelope, dict) else None
|
|
174
|
+
if not isinstance(data, list):
|
|
175
|
+
data = []
|
|
176
|
+
total = envelope.get("total") if isinstance(envelope, dict) else None
|
|
177
|
+
return {"data": data, "total": total if isinstance(total, int) else len(data)}
|
|
178
|
+
|
|
179
|
+
def request_envelope(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
180
|
+
envelope, _, _ = self._parse(path, params)
|
|
181
|
+
return envelope
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: domainkits
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the DomainKits REST API. Search expiring, newly registered, aged, active, deleted and marketplace domains, plus WHOIS, DNS, Certificate Transparency and trends.
|
|
5
|
+
Author-email: DomainKits <info@domainkits.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://domainkits.com/dev
|
|
8
|
+
Project-URL: Documentation, https://domainkits.com/dev/api-docs
|
|
9
|
+
Project-URL: Repository, https://github.com/ABTdomain/domainkits-sdk-py
|
|
10
|
+
Keywords: domain,domain-search,expired-domains,newly-registered-domains,whois,dns,certificate-transparency,domainkits
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# domainkits
|
|
26
|
+
|
|
27
|
+
Python client for the [DomainKits](https://domainkits.com) REST API.
|
|
28
|
+
|
|
29
|
+
DomainKits is one API with a shared key across every endpoint. This package covers all of them, six domain search types, WHOIS, DNS, reverse nameserver, Certificate Transparency, safety, trends and bulk download, with automatic paging and structured quota errors. No dependencies.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
The REST API is for Premium and Platinum accounts; unauthenticated requests are rejected with 401. Keys start with `dk_` and come from [domainkits.com](https://domainkits.com/pricing).
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install domainkits
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import os
|
|
45
|
+
from domainkits import DomainKits
|
|
46
|
+
|
|
47
|
+
dk = DomainKits(os.environ["DOMAINKITS_API_KEY"])
|
|
48
|
+
|
|
49
|
+
result = dk.expired.list(
|
|
50
|
+
keyword="clinic",
|
|
51
|
+
tld="com",
|
|
52
|
+
length="5-10",
|
|
53
|
+
no_number=True,
|
|
54
|
+
no_hyphen=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
print(f"{result['total']} matches")
|
|
58
|
+
for d in result["data"]:
|
|
59
|
+
print(d["domain"], d["age"], d["status"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Paging
|
|
63
|
+
|
|
64
|
+
A single request returns at most 500 results. `paginate` walks the whole result set for you:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
for domain in dk.expired.paginate(keyword="clinic", tld="com"):
|
|
68
|
+
print(domain["domain"])
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It stops when the result set is exhausted. Break out of the loop whenever you have enough; no further requests are made.
|
|
72
|
+
|
|
73
|
+
### Export
|
|
74
|
+
|
|
75
|
+
`export` pulls up to 50,000 rows as CSV in one request:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
csv = dk.expired.export(tld="com", status="pending_delete")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This runs on a separate, much smaller quota, 10 per day and 100 per month on Premium, 3 and 9 during the trial. It is for occasional bulk pulls, not for a scheduled job. The export also returns fewer columns than paged mode: `registered_date` is the year only, and `age` is omitted.
|
|
82
|
+
|
|
83
|
+
50,000 is a cap, not a promise of completeness. Browsing `.com` matched 4,847,613 expiring domains on 27 July 2026, so an unfiltered export returns the first 50,000. Narrow the query if you need the result set to fit.
|
|
84
|
+
|
|
85
|
+
## Search types
|
|
86
|
+
|
|
87
|
+
| Attribute | What it searches |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `dk.expired` | Domains in the deletion cycle: expired, redemption, pending delete |
|
|
90
|
+
| `dk.nrds` | Newly registered domains, last 60 days |
|
|
91
|
+
| `dk.aged` | Domains with 5 to 20+ years of registration history |
|
|
92
|
+
| `dk.active` | Currently registered domains |
|
|
93
|
+
| `dk.deleted` | Dropped domains (requires `keyword`) |
|
|
94
|
+
| `dk.market` | Domains listed for sale on marketplaces |
|
|
95
|
+
|
|
96
|
+
Each has `list`, `paginate` and `export`. Filters are passed as keyword arguments and match the REST parameter names: an expired result carries `status`, an NRD result carries `expiry_date`, a market result carries `marketplace`.
|
|
97
|
+
|
|
98
|
+
## Other endpoints
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
dk.whois("example.com")
|
|
102
|
+
dk.dns("example.com")
|
|
103
|
+
dk.safety("example.com")
|
|
104
|
+
dk.ns_reverse("ns1.example.com", tld="com")
|
|
105
|
+
dk.tld_check("yourbrand")
|
|
106
|
+
dk.typosquat("example.com")
|
|
107
|
+
dk.ip_lookup("8.8.8.8")
|
|
108
|
+
dk.registrar("godaddy")
|
|
109
|
+
dk.status_guide("clientHold")
|
|
110
|
+
dk.monitor_changes(tld="com", reason="transfer")
|
|
111
|
+
dk.ct_subdomains("example.com")
|
|
112
|
+
dk.ct_certs(domain="example.com")
|
|
113
|
+
dk.ct_search("example")
|
|
114
|
+
dk.tld_trends("newly", tld="com")
|
|
115
|
+
dk.keyword_trends("hot")
|
|
116
|
+
dk.usage()
|
|
117
|
+
dk.search_status()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Coverage
|
|
121
|
+
|
|
122
|
+
**gTLDs only** for the domain search endpoints. The index covers generic TLDs: `.com`, `.net`, `.org`, `.info`, `.biz`, `.xyz`, `.online`, `.site`, `.top`, `.club`, `.live`, `.app`, `.dev` and others. Country-code TLDs are not indexed: a query for `.de`, `.io`, `.co` or `.us` returns an empty result set, not an error.
|
|
123
|
+
|
|
124
|
+
`whois`, `dns`, `safety`, `ip_lookup` and the Certificate Transparency endpoints work on any domain, ccTLDs included.
|
|
125
|
+
|
|
126
|
+
**No PII.** Responses contain no personal data. WHOIS results are limited to registrar, dates, status codes and nameservers; registrant names, emails, addresses and phone numbers are not returned.
|
|
127
|
+
|
|
128
|
+
## Errors
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from domainkits import DomainKits, DomainKitsError, RateLimitError, AuthError
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
dk.expired.export(tld="com")
|
|
135
|
+
except RateLimitError as error:
|
|
136
|
+
print(f"Quota exhausted. Retry in {error.retry_after_ms}ms")
|
|
137
|
+
print(error.rate_limit)
|
|
138
|
+
except AuthError:
|
|
139
|
+
print("Key rejected, check your plan tier")
|
|
140
|
+
except DomainKitsError as error:
|
|
141
|
+
print(error.status, error)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every error carries the `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` values as a parsed `rate_limit` object. `RateLimitError.retry_after_ms` tells you how long until the window resets.
|
|
145
|
+
|
|
146
|
+
429 and 5xx responses are retried automatically, twice by default, waiting until the rate-limit window resets when that is under two minutes. Set `max_retries=0` to handle it yourself.
|
|
147
|
+
|
|
148
|
+
## Options
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
dk = DomainKits(
|
|
152
|
+
api_key=os.environ["DOMAINKITS_API_KEY"],
|
|
153
|
+
timeout=60.0,
|
|
154
|
+
max_retries=2,
|
|
155
|
+
base_url="https://premium-api.domainkits.com/api/v1",
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Quotas
|
|
160
|
+
|
|
161
|
+
Call `usage()` for the live picture on your account; every endpoint reports its own per-minute, daily and monthly allowance alongside what you have already spent.
|
|
162
|
+
|
|
163
|
+
Daily quotas reset at 00:00 UTC, monthly quotas on the 1st. Current limits: [domainkits.com/dev/api-docs](https://domainkits.com/dev/api-docs).
|
|
164
|
+
|
|
165
|
+
## Resources
|
|
166
|
+
|
|
167
|
+
- [DomainKits API reference](https://domainkits.com/dev/api-docs)
|
|
168
|
+
- [@domainkits/sdk](https://www.npmjs.com/package/@domainkits/sdk), the same API for TypeScript
|
|
169
|
+
- [n8n-nodes-domainkits](https://www.npmjs.com/package/n8n-nodes-domainkits), the same API for n8n
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
domainkits
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "domainkits"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the DomainKits REST API. Search expiring, newly registered, aged, active, deleted and marketplace domains, plus WHOIS, DNS, Certificate Transparency and trends."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "DomainKits", email = "info@domainkits.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["domain", "domain-search", "expired-domains", "newly-registered-domains", "whois", "dns", "certificate-transparency", "domainkits"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
26
|
+
]
|
|
27
|
+
requires-python = ">=3.9"
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://domainkits.com/dev"
|
|
32
|
+
Documentation = "https://domainkits.com/dev/api-docs"
|
|
33
|
+
Repository = "https://github.com/ABTdomain/domainkits-sdk-py"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["domainkits*"]
|