{"id":23421499,"url":"https://github.com/domaintools/python_api","last_synced_at":"2026-05-15T18:01:01.107Z","repository":{"id":12189428,"uuid":"71191969","full_name":"DomainTools/python_api","owner":"DomainTools","description":"DomainTools Official Python API","archived":false,"fork":false,"pushed_at":"2026-01-16T14:36:49.000Z","size":147770,"stargazers_count":86,"open_issues_count":7,"forks_count":33,"subscribers_count":10,"default_branch":"main","last_synced_at":"2026-01-17T20:12:18.626Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DomainTools.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2016-10-18T00:12:20.000Z","updated_at":"2026-01-16T14:35:43.000Z","dependencies_parsed_at":"2026-01-17T11:06:51.277Z","dependency_job_id":null,"html_url":"https://github.com/DomainTools/python_api","commit_stats":{"total_commits":313,"total_committers":22,"mean_commits":"14.227272727272727","dds":0.7571884984025559,"last_synced_commit":"6c65f054745ea27787adf5ccc4f0a78b3302e859"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/DomainTools/python_api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DomainTools%2Fpython_api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DomainTools%2Fpython_api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DomainTools%2Fpython_api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DomainTools%2Fpython_api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DomainTools","download_url":"https://codeload.github.com/DomainTools/python_api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DomainTools%2Fpython_api/sbom","scorecard":{"id":41628,"data":{"date":"2025-08-11","repo":{"name":"github.com/DomainTools/python_api","commit":"c638c8588a45b31a1e083f3fe991037b5f93d5ad"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.6,"checks":[{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test-build-publish.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:73: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:75: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:88: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:101: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test-build-publish.yml:107: update your workflow using https://app.stepsecurity.io/secureworkflow/DomainTools/python_api/test-build-publish.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .env:43","Warn: pipCommand not pinned by hash: .env:110","Warn: pipCommand not pinned by hash: .env:116","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:25","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:26","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:47","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:48","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:49","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:81","Warn: pipCommand not pinned by hash: .github/workflows/test-build-publish.yml:82","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of  10 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/test-build-publish.yml:93"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-14T21:42:36.710Z","repository_id":12189428,"created_at":"2025-08-14T21:42:36.710Z","updated_at":"2025-08-14T21:42:36.710Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33074385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-23T02:15:32.495Z","updated_at":"2026-05-15T18:01:01.026Z","avatar_url":"https://github.com/DomainTools.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![domaintools](https://github.com/DomainTools/python_api/raw/main/artwork/logo.png)\n===================\n\n[![PyPI version](https://badge.fury.io/py/domaintools_api.svg)](http://badge.fury.io/py/domaintools_api)\n[![CI Status](https://github.com/domaintools/python_api/workflows/Tests/badge.svg)](https://github.com/domaintools/python_api/actions)\n[![Coverage Status](https://coveralls.io/repos/github/DomainTools/python_api/badge.svg?branch=main)](https://coveralls.io/github/DomainTools/python_api?branch=main)\n[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://pypi.python.org/pypi/domaintools_api/)\n\nDomainTools Official Python API\n\n![domaintools Example](https://github.com/DomainTools/python_api/raw/main/artwork/example.gif)\n\nThe DomainTools Python API Wrapper provides an interface to work with our cybersecurity and related data tools provided by our Iris Investigate™, Iris Enrich™, and Iris Detect™ products. It is actively maintained and may be downloaded via \u003ca href=\"https://github.com/DomainTools/python_api\"\u003eGitHub\u003c/a\u003e or \u003ca href=\"https://pypi.org/project/domaintools-api/\"\u003ePyPI\u003c/a\u003e. See the included README file, the examples folder, and API documentation (https://app.swaggerhub.com/apis-docs/DomainToolsLLC/DomainTools_APIs/1.0#) for more info.\n\nInstalling the DomainTools API\n===================\n\nTo install the API run\n\n```bash\npip install domaintools_api --upgrade\n```\n\nIdeally, within a virtual environment.\n\n\nUsing the API\n===================\n\nTo start out create an instance of the API - passing in your credentials\n\n```python\n\nfrom domaintools import API\n\n\napi = API(USER_NAME, KEY)\n```\n\nEvery API endpoint is then exposed as a method on the API object, with any parameters that should be passed into that endpoint\nbeing passed in as method arguments:\n\n```python\napi.iris_enrich('domaintools.com')\n```\n\nYou can get an overview of every endpoint that you can interact with using the builtin help function:\n\n```python\nhelp(api)\n```\n\nOr if you know the endpoint you want to use, you can get more information about it:\n\n```python\nhelp(api.iris_investigate)\n```\n\nIf applicable, native Python looping can be used directly to loop through any results:\n\n```python\nfor result in api.iris_enrich('domaintools.com').response().get('results', {}):\n    print(result['domain'])\n```\n\nYou can also use a context manager to ensure processing on the results only occurs if the request is successfully made:\n\n```python\nwith api.iris_enrich('domaintools.com').response().get('results', {}) as results:\n    print(results)\n```\n\nFor API calls where a single item is expected to be returned, you can directly interact with the result:\n\n```python\nprofile = api.domain_profile('google.com')\ntitle = profile['website_data']['title']\n```\n\nFor any API call where a single type of data is expected you can directly cast to the desired type:\n\n```python\nfloat(api.reputation('google.com')) == 0.0\nint(api.reputation('google.com')) == 0\n```\n\nThe entire structure returned from DomainTools can be retrieved by doing `.data()` while just the actionable response information\ncan be retrieved by doing `.response()`:\n\n```python\napi.iris_enrich('domaintools.com').data() == {'response': { ... }}\napi.iris_enrich('domaintools.com').response() == { ... }\n```\n\nYou can directly get the html, xml, or json version of the response by calling `.(html|xml|json)()` These only work with non AsyncResults:\n```python\njson = str(api.domain_search('google').json())\nxml = str(api.domain_search('google').xml())\nhtml = str(api.domain_search('google').html())\n```\n\nIf any API call is unsuccesfull, one of the exceptions defined in `domaintools.exceptions` will be raised:\n\n```python-traceback\napi.domain_profile('notvalid').data()\n\n\n---------------------------------------------------------------------------\nBadRequestException                       Traceback (most recent call last)\n\u003cipython-input-3-f9e22e2cf09d\u003e in \u003cmodule\u003e()\n----\u003e 1 api.domain_profile('google').data()\n\n/home/tcrosley/projects/external/python_api/venv/lib/python3.5/site-packages/domaintools-0.0.1-py3.5.egg/domaintools/base_results.py in data(self)\n     25                 self.api._request_session = Session()\n     26             results = self.api._request_session.get(self.url, params=self.kwargs)\n---\u003e 27             self.status = results.status_code\n     28             if self.kwargs.get('format', 'json') == 'json':\n     29                 self._data = results.json()\n\n/home/tcrosley/projects/external/python_api/venv/lib/python3.5/site-packages/domaintools-0.0.1-py3.5.egg/domaintools/base_results.py in status(self, code)\n     44\n     45         elif code == 400:\n---\u003e 46             raise BadRequestException()\n     47         elif code == 403:\n     48             raise NotAuthorizedException()\n\nBadRequestException:\n\n```\n\nthe exception will contain the status code and the reason for the exception:\n\n```python\ntry:\n    api.domain_profile('notvalid').data()\nexcept Exception as e:\n    assert e.code == 400\n    assert 'We could not understand your request' in e.reason['error']['message']\n```\n\nYou can get the status code of a response outside of exception handling by doing `.status`:\n\n```python\n\napi.domain_profile('google.com').status == 200\n```\n\nUsing the API Asynchronously\n===================\n\n![domaintools Async Example](https://github.com/DomainTools/python_api/raw/main/artwork/example_async.gif)\n\nThe DomainTools API automatically supports async usage:\n\n```python\n\nsearch_results = await api.iris_enrich('domaintools.com').response().get('results', {})\n```\n\nThere is built-in support for async context managers:\n\n```python\nasync with api.iris_enrich('domaintools.com').response().get('results', {}) as search_results:\n    # do things\n```\n\nAnd direct async for loops:\n\n```python\nasync for result in api.iris_enrich('domaintools.com').response().get('results', {}):\n    print(result)\n```\n\nAll async operations can safely be intermixed with non async ones - with optimal performance achieved if the async call is done first:\n```python\nprofile = api.domain_profile('google.com')\nawait profile\ntitle = profile['website_data']['title']\n```\n\nInteracting with the API via the command line client\n===================\n\n![domaintools CLI Example](https://github.com/DomainTools/python_api/raw/main/artwork/example_cli.gif)\n\nImmediately after installing `domaintools_api` with pip, a `domaintools` command line client will become available to you:\n\n```bash\ndomaintools --help\n```\n\nTo use - simply pass in the api_call you would like to make along with the parameters that it takes and your credentials:\n\n```bash\ndomaintools iris_investigate --domains domaintools.com -u $TEST_USER -k $TEST_KEY\n```\n\nOptionally, you can specify the desired format (html, xml, json, or list) of the results:\n\n```bash\ndomaintools domain_search google --max_length 10 -u $TEST_USER -k $TEST_KEY -f html\n```\n\nTo avoid having to type in your API key repeatedly, you can specify them in `~/.dtapi` separated by a new line:\n\n```bash\nAPI_USER\nAPI_KEY\n```\n\nPython Version Support Policy\n===================\n\nPlease see the [supported versions](https://github.com/DomainTools/python_api/raw/main/PYTHON_SUPPORT.md) document\nfor the DomainTools Python support policy.\n\n\nReal-Time Threat Feeds\n===================\n\nReal-Time Threat Feeds provide data on the different stages of the domain lifecycle: from first-observed in the wild, to newly re-activated after a period of quiet. Access current feed data in real-time or retrieve historical feed data through separate APIs.\n\nCustom parameters aside from the common `GET` Request parameters:\n- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)\n    ```python\n    api = API(USERNAME, KEY)\n    api.nod(endpoint=\"feed\", **kwargs)\n    ```\n- `header_authentication`: by default, we're using API Header Authentication. Set this False if you want to use API Key and Secret Authentication. Apparently, you can't use API Header Authentication for `download` endpoints so this will be defaulted to `False` even without explicitly setting it.\n    ```python\n    api = API(USERNAME, KEY, header_authentication=False)\n    api.nod(**kwargs)\n    ```\n- `output_format`: (choose either `csv` or `jsonl` - default is `jsonl`). Cannot be used in `domainrdap` feeds. Additionally, `csv` is not available for `download` endpoints.\n    ```python\n    api = API(USERNAME, KEY)\n    api.nod(output_format=\"csv\", **kwargs)\n    ```\n\nThe Feed API standard access pattern is to periodically request the most recent feed data, as often as every 60 seconds. Specify the range of data you receive in one of two ways:\n\n1. With `sessionID`: Make a call and provide a new `sessionID` parameter of your choosing. The API will return the last hour of data by default.\n    - Each subsequent call to the API using your `sessionID` will return all data since the last.\n    - Any single request returns a maximum of 10M results. Requests that exceed 10M results will return a HTTP 206 response code; repeat the same request (with the same `sessionID`) to receive the next tranche of data until receiving a HTTP 200 response code.\n2. Or, specify the time range in one of two ways:\n    - Either an `after=-60` query parameter, where (in this example) -60 indicates the previous 60 seconds.\n    - Or `after` and `before` query parameters for a time range, with each parameter accepting an ISO-8601 UTC formatted timestamp (a UTC date and time of the format YYYY-MM-DDThh:mm:ssZ)\n\n## Handling iterative response from RTUF endpoints:\n\nSince we may dealing with large feeds datasets, the python wrapper uses `generator` for efficient memory handling. Therefore, we need to iterate through the `generator` if we're accessing the partial results of the feeds data.\n\n### Single request because the requested data is within the maximum result:\n```python\nfrom domaintools import API\n\napi = API(USERNAME, KEY)\nresults = api.nod(sessionID=\"my-session-id\", after=-60)\n\nfor result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once\n    # do things to result\n```\n\n## Multiple requests because the requested data is more than the maximum result per request:\n```python\nfrom domaintools import API\n\napi = API(USERNAME, KEY)\nresults = api.nod(sessionID=\"my-session-id\", after=-7200)\n\nfor partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times\n    # do things to partial_result\n```\n\n\nRunning E2E Tests Locally\n===================\nFor now, e2e tests only covers proxy and ssl testing. We are expected to broaden our e2e tests to other scenarios moving forward.\nTo add more e2e tests, put these in the `../tests/e2e` folder.\n\n## Preparation\n- Create virtual environment.\n    ```bash\n        python3 -m venv venv\n    ```\n\n- Activate virtual environment\n    ```bash\n        source venv/bin/activate\n    ```\n\n- Install dependencies.\n    ```bash\n        pip install -r requirements/development.txt\n    ```\n\n- From the python_api project root directory, install the package.\n    ```bash\n        pip install -e .\n    ```\n\n- Export api credentials to use.\n    ```bash\n        export TEST_USER=\u003cuser-key\u003e\n        export TEST_KEY=\u003capi-key\u003e\n    ```\n- Run unit tests.\n    ```bash\n        tox -e\n    ```\n\n## Run the end-to-end test script\n- Before running the test, be sure that docker is running.\n- Execute the e2e test script .\n    ```bash\n        sh tests/e2e/scripts/test_e2e_runner.sh\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomaintools%2Fpython_api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdomaintools%2Fpython_api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomaintools%2Fpython_api/lists"}