PyFederation
PyFederation is a Python client library for the Federated Database protocol. It implements the Open Federated Database specification and follows the design of the reference implementation, FederationLib, and of its Java counterpart, JFederation, from which it is ported.
Features
- Works with any OFD-compliant Federation server
- Content scanning with entity resolution, classification, and risk assessment
- Every method of the server API: operators, records, evidence, reports, blacklist records, file attachments, audit logs, search, and server information
- Pagination, category filters, and sorting on every listing and search endpoint
- Synchronous and asynchronous clients,
FederationClientandAsyncFederationClient, with the same methods - All errors are mapped to a single exception carrying the HTTP status code and server message
Table of Contents
Prerequisites
- Python 3.11 or newer
- A running Federation server for anything other than constructing a client, the reference implementation is FederationLib, which includes a dockerized server deployment
Dependencies are HTTPX for HTTP transport and Pydantic for the records.
Installation
Via PyPI
The library is published on PyPI:
pip install pyfederation
From source
To use an unreleased version, install the library from a clone of the repository:
git clone https://github.com/nosial/PyFederation.git
cd PyFederation
pip install .
Usage
All functionality is exposed through the FederationClient class. Each method maps to a REST endpoint of the
Federation server and handles serialization, authentication, and error mapping.
Connecting to a server
A client is constructed with the base URL of the Federation server and an optional operator access token. A client without a token operates anonymously against the server's public endpoints:
from pyfederation import FederationClient
# Anonymous client
client = FederationClient("https://federation.example.com")
# Authenticated client with an operator access token
client = FederationClient("https://federation.example.com", "access-token")
The access token can be changed at any time during the lifetime of the client:
client.access_token = "new-token" # switch authentication
client.access_token = None # revert to anonymous access
Clients should be closed when no longer needed to release the underlying HTTP connection pool, or used as a context manager:
client.close()
with FederationClient("https://federation.example.com", "access-token") as client:
...
For custom HTTP settings (timeouts, proxies, TLS, etc.) an existing httpx.Client can be passed to
the constructor instead:
import httpx
http_client = httpx.Client(timeout=15)
client = FederationClient("https://federation.example.com", "access-token", http_client)
Server information, including the server name, API version, feature flags (which record types are
publicly visible), and total record counts, is available through get_server_information(), the raw
OpenAPI specification of the server is available through get_specification():
info = client.get_server_information()
print(f"Server {info.name}, API version {info.api_version}")
print(f"Known entities: {info.known_entities}, reports: {info.reports}")
Asynchronous client
AsyncFederationClient has the same methods as FederationClient, as coroutines, and is closed with
aclose() or used as an async context manager:
from pyfederation import AsyncFederationClient
async with AsyncFederationClient("https://federation.example.com", "access-token") as client:
info = await client.get_server_information()
Content scanning
Content scanning accepts text, a ContentInput, or a list of inputs. Each input can carry text, an
operator note, tag, confidentiality flag, and metadata; at least one input must contain text:
| Method | Return Type | Description |
|---|---|---|
scan_content(evidence, author, top_k, threshold) |
ScannedContent |
Scans one or more content inputs |
Scanning content and inspecting the classification result:
from pyfederation import ContentInput
scan = client.scan_content(ContentInput("Suspicious message to inspect"), "author@example.com")
classification = scan.classification
print(
f"Classification {classification.classification_flag} with "
f"{classification.confidence:.1%} confidence, detected language: {classification.detected_language}"
)
Searching records
The global search queries all record types with an optional type filter. The search query must be at least 2 characters:
from pyfederation import RecordType
# Search all record types
results = client.search("author@example.com")
# Search only entity and blacklist records, page 1, 25 per page
results = client.search("author@example.com", [RecordType.ENTITY, RecordType.BLACKLIST], 1, 25)
SearchResult records carry the matched type and the record, resolved to the record class of
the matched type.
Operators
Operators are the accounts of the Federation server. Every change to the data is made by an operator, and each operator holds permissions that decide what it may do.
| Method | Description |
|---|---|
create_operator(name) |
Creates a new operator, returns the raw access token (only exposed at creation time) |
get_operator(uuid) / get_self() |
Returns an operator record / the currently authenticated operator |
list_operators(page, limit, category, by, order) |
Lists operators with optional filters |
search_operators(query, page, limit, category, by, order) |
Searches operators by name |
update_operator_name(uuid, name) |
Updates the display name of an operator |
delete_operator(uuid) |
Deletes an operator |
disable_operator(uuid) / enable_operator(uuid) |
Disables / enables an operator account |
set_operator_permissions(uuid, enabled) |
Sets whether the operator has operator-level permissions |
set_client_permissions(uuid, enabled) |
Sets whether the operator has client-level permissions |
set_management_permissions(uuid, enabled) |
Sets whether the operator has management-level permissions |
set_auto_assign(uuid, enabled) |
Sets whether reports are automatically assigned to the operator |
generate_access_token(update) |
Refreshes the current operator's token, optionally updating the client |
generate_operator_access_token(uuid) |
Refreshes another operator's token |
list_operator_audit_logs(uuid, ...) |
Lists the audit log entries of an operator |
list_operator_evidence(uuid, ...) |
Lists the evidence an operator submitted |
list_operator_blacklist(uuid, ...) |
Lists the blacklist records created by an operator |
list_operator_reports(uuid, ...) / list_assigned_operator_reports(uuid, ...) |
Lists the reports submitted by / assigned to an operator |
Creating an operator and generating a new token:
created = client.create_operator("my-application")
print(f"Operator UUID: {created.uuid}")
# The raw token is only returned once at creation time, store it securely
token = created.access_token
refreshed = client.generate_access_token(True) # refresh and update this client
Entities
Entities are the subjects tracked by the federation: users, domains, IP addresses, or any other identifier present in scanned content.
| Method | Description |
|---|---|
get_entity_record(identifier) |
Returns the record of an entity by identifier |
query_entity(identifier) |
Returns an entity's relationship group and active blacklist state |
search_entities(query, page, limit, category, by, order) |
Searches entities by identifier |
list_entities(page, limit, category, by, order) |
Lists entities |
get_top_threats(limit) |
Lists the highest-risk entities |
update_entity(identifier, metadata) |
Updates the metadata of an entity |
delete_entity(identifier) |
Deletes an entity |
set_entity_whitelist(identifier, whitelisted) |
(Un)whitelists an entity |
clear_entity_reputation(identifier) |
Clears the accumulated reputation of an entity |
set_entity_relationship(identifier, related_id, type) |
Sets the relationship between two entities |
clear_entity_relationship(identifier) |
Clears the relationship of an entity |
push_entity(host, id, metadata) |
Registers an entity with the server |
list_entity_audit_logs(identifier, ...) |
Lists the audit log entries involving an entity |
list_entity_blacklist_records(identifier, ...) |
Lists the blacklist records against an entity |
list_entity_evidence_records(identifier, ...) |
Lists the evidence submitted against an entity |
list_entity_reports(identifier, ...) |
Lists the reports submitted against an entity |
Entities are identified by their UUID, SHA-256 hash, or address, such as example.com or
john@example.com.
Evidence
Evidence records are the supporting data attached to entities, either when submitting a report or manually by operators, and can hold text content, attachments, tags, notes, and confidentiality flags:
| Method | Description |
|---|---|
submit_evidence(identifier, text_content, note, tag, confidential, metadata, classification) |
Submits evidence with all optional fields |
list_evidence(page, limit, include_confidential, category, by, order) |
Lists evidence records |
search_evidence(query, page, limit, category, by, order) |
Searches evidence records |
get_evidence_record(uuid) |
Returns a single evidence record |
get_evidence_attachments(uuid) |
Lists the attachments of an evidence record |
update_evidence_confidentiality(uuid, confidential) |
Updates the confidentiality flag of evidence |
update_evidence_tag(uuid, tag) |
Updates the tag of evidence |
classify_evidence(uuid, classification) |
Classifies evidence |
add_evidence_to_report(evidence_uuid, report_uuid) |
Attaches evidence to a report |
delete_evidence(uuid) |
Deletes an evidence record |
Confidential evidence is hidden from unauthenticated clients by default, listing endpoints
support an include_confidential parameter to include them when the operator has the appropriate
permissions.
Reports
Reports are the central mechanism of the federation server. A report is submitted against an entity, groups one or more pieces of evidence, and can be classified, assigned to operators, and closed:
| Method | Description |
|---|---|
submit_report(entity, evidence, incident_type, report_message) |
Submits one or more evidence items with a report |
list_reports(page, limit, category, by, order) |
Lists reports |
list_opened_reports(page, limit, by, order) |
Lists the opened reports assigned to the current operator |
search_reports(query, page, limit, category, by, order) |
Searches reports by content |
get_report(uuid) |
Returns a single report |
list_report_evidence_records(report_uuid, ...) |
Lists evidence associated with a report |
close_report(uuid, classification, blacklist_incident_type, blacklist_expires) |
Closes a report, optionally classifying its evidence and blacklisting the entity |
assign_operator_to_report(report_uuid, operator_uuid) |
Assigns an operator to a report |
delete_report(uuid) |
Deletes a report |
submit_report returns a ReportSubmission object with the created report and its list of
evidence records. Upload attachments separately against an evidence record.
Blacklist records
Blacklist records are the server's mitigation mechanism. An operator blacklists an entity with an incident type and, optionally, the report backing the decision:
| Method | Description |
|---|---|
blacklist_entity(identifier, incident_type, report_uuid) |
Blacklists an entity permanently |
blacklist_entity(identifier, incident_type, report_uuid, expires) |
Blacklists an entity with an expiration timestamp (Unix epoch seconds) |
list_blacklist_records(page, limit, include_lifted, category, by, order) |
Lists blacklist entries |
search_blacklist(query, page, limit, category, by, order) |
Searches blacklist entries |
get_blacklist_record(uuid) |
Returns a single blacklist record |
lift_blacklist_record(uuid) |
Lifts the blacklist entry of an entity |
extend_blacklist_record(uuid, seconds) |
Extends the blacklist of an entity by the given seconds |
delete_blacklist_record(uuid) |
Deletes a blacklist record |
File attachments
Evidence records support file attachments with uploads from disk or URL, and note attachments (plain-text content):
| Method | Description |
|---|---|
upload_file_attachment(evidence_uuid, local_file_path) |
Uploads a local file attachment |
upload_file_attachment(evidence_uuid, local_file_path, file_name) |
Uploads a local file attachment with a custom name |
upload_file_attachment_from_url(evidence_uuid, file_url) |
Uploads an attachment from a reachable URL |
upload_file_attachment_from_url(evidence_uuid, file_url, max_file_size) |
Uploads an attachment from a URL with a size limit |
upload_note_attachment(evidence_uuid, file_name, content) |
Uploads a plain-text note attachment |
download_attachment(attachment_uuid, directory_path) |
Downloads an attachment into the given directory |
get_attachment_info(uuid) |
Returns the metadata of an attachment |
list_attachments(page, limit, category, by, order) |
Lists attachments |
search_attachments(query, page, limit, category, by, order) |
Searches attachments |
delete_attachment(uuid) |
Deletes an attachment |
URL uploads default to a maximum file size of 50 MB, matching the FederationLib client default, the limit can be raised per request.
Pagination and sorting
Every listing and search endpoint accepts the same pagination and sorting arguments: page (1-based)
and limit (minimum 1), followed by an optional category filter, by (the field to sort by,
case-insensitive), and order ("ASC" or "DESC"), for example:
# Defaults: page 1, limit 100
entities = client.list_entities()
# Explicit pagination
entities = client.list_entities(2, 50)
# Pagination with a category filter, sorted by reputation ascending
entities = client.list_entities(2, 50, "WHITELISTED", "reputation", "ASC")
The argument order is the same for every endpoint.
Error handling
All failures are raised as FederationClientException, carrying the HTTP status code of the
response and, when the server returns one, its error message:
from pyfederation import FederationClientException
try:
client.get_entity_record("non-existent-entity")
except FederationClientException as e:
print(f"Request failed with status {e.status_code}: {e}")
Failures without a response, such as connection errors, have a status code of 0. Invalid arguments,
such as an empty UUID, a page below 1, a search query shorter than 2 characters, or an access token
that is empty or contains whitespace, are rejected by the client immediately with a ValueError.
Testing
The unit tests run without a server:
pip install -e . --group dev
pytest
The integration tests run against a live Federation server, and the repository includes a
docker-compose.yml that brings up the reference
FederationLib server, MariaDB, and Redis on http://localhost:7000:
docker compose up -d
pytest -m integration
By default the tests expect the server at http://localhost:7000 with access token
abcdefghijklmnopqrstuvwxyz123456 (matching the compose defaults). To test against a different
server, set the SERVER_ENDPOINT and SERVER_ACCESS_TOKEN environment variables:
SERVER_ENDPOINT=http://localhost:7000 SERVER_ACCESS_TOKEN=my-token pytest -m integration
The synchronous client, client.py, is generated from the asynchronous client, async_client.py,
run python scripts/unasync.py after changing it.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Metadata
Release files for pyfederation 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pyfederation-1.0.0.tar.gz | 66.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyfederation-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.0 kB
Release files / pyfederation-1.0.0.tar.gz
| Download URL | pyfederation-1.0.0.tar.gz |
|---|---|
| Size | 66.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b40a5ab5d18c346c00d5878093d457eda96b1c1526d197d7eb6469fa470f5053
|
|
BLAKE2b-256 checksum How to use checksums |
152ed8518f51258a3dd42f28a74395fc9b26c37acb85426d1b529afc311dac7a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / pyfederation-1.0.0-py3-none-any.whl
| Download URL | pyfederation-1.0.0-py3-none-any.whl |
|---|---|
| Size | 31.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a7802b48534c41aaff7f50d71af47c1ff78f9a8c8bb95c277353fbcbf2f27b9b
|
|
BLAKE2b-256 checksum How to use checksums |
2c6b30c9a0f9548256a52776ae11095c42101b741710402fc5a11bc8ca06d183
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for pyfederation 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pyfederation-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.