Skip to main content

Proofpoint Secure Email Relay Admin API

PyPI Downloads
Library implements the Proofpoint Secure Email Relay administrative APIs via Python.

Requirements:

  • Python 3.11+
  • klarient
  • requests
  • requests-oauth2client

Installing the Package

You can install the API library using the following command directly from Github.

pip install git+https://github.com/pfptcommunity/ser-admin-api-python.git

or can install the API library using pip.

pip install ser-admin-api

For local development from this folder, use an editable install.

python3 -m pip install -e .

Creating an API client object

SER credentials are issued as a principal and secret. SERClient handles the OAuth client-credentials exchange internally using Proofpoint's token endpoint, then applies the issued token to each SER service host.

from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

The client can also be used as a context manager.

from ser_admin_api import SERClient

if __name__ == '__main__':
    with SERClient("<enter_your_principal_here>", "<enter_your_secret_here>") as client:
        print(client.relay.relay_users.path)

Endpoint Examples

Endpoint-focused examples are available under examples/.

examples/
  connector_discovery.py
  connectors.py
  list_management_requests.py
  list_management_unsubscribe.py
  relay_config_discovery.py
  relay_users.py
  reporting_failures.py
  reporting_scoped_resources.py
  reporting_usage.py
  tag_management.py

Copy examples/settings.example.json to settings.json at the project root and add your principal and secret to run them locally. The examples also accept examples/settings.json if you prefer to keep local example settings beside the example files.

The examples are intentionally resource-focused so each file demonstrates one small part of the SER API tree. The runnable portions are read-only by default. Mutation examples are shown as request shapes or commented calls because connector and relay-user delete operations are not exposed by the documented API.

Maintainer Smoke Tests

Release smoke tests live under tools/live_smoke/. They are separate from the user-facing examples and require live SER Admin API credentials.

See tools/live_smoke/README.md.

Resource Paths

The API is modeled as a resource tree. Each resource exposes its path and URL, which can be useful when learning or debugging the wrapper.

from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    print(client.relay.relay_users.path)
    # /v1/relay-users

    print(client.connector_config.connectors.details.path)
    # /v1/connectors/details

    print(client.list_management.lists.unsubscribe.requests.url)
    # https://list-management.ser.proofpoint.com/v1/lists/unsubscribe/requests

Modeled API Roots

The SER documentation exposes separate hosts for the API areas. SERClient is a facade that owns one Klarient client per service host while sharing one OAuth-backed session.

  • Reporting: https://reporting.ser.proofpoint.com
  • Reporting v2: https://reporting.ser.proofpoint.com
  • Relay User Management: https://relay-config.ser.proofpoint.com
  • Connector Management: https://connector-config.ser.proofpoint.com
  • Tag Management: https://tag-management.ser.proofpoint.com
  • List Management: https://list-management.ser.proofpoint.com

You can override any base URL in the constructor for tests or private deployments.

Querying Relay Users

from ser_admin_api import SERClient
from ser_admin_api.relay_users import RelayUsersQuery

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    relay_users = client.relay.relay_users.retrieve(RelayUsersQuery(page=1, size=100))
    page = relay_users.page

    print("Status: {}".format(page.status))
    print("Reason: {}".format(page.reason))
    print("Total Records: {}".format(page.record_count))

    for relay_user in page.data:
        print(relay_user.relay_user_id)
        print(relay_user.name)
        print(relay_user.status)

Querying Connectors

from ser_admin_api import SERClient
from ser_admin_api.connectors import ConnectorInfoQuery

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    connectors = client.connector_config.connectors.retrieve(ConnectorInfoQuery(page=1, size=100))

    for connector in connectors.data:
        print(connector.connector_id)
        print(connector.name)
        print(connector.status)

Querying Tags

from ser_admin_api import SERClient
from ser_admin_api.tags import TagInfoQuery

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    tags = client.tag_management.tags.retrieve(TagInfoQuery(page=1, size=100))

    for tag in tags.data:
        print(tag.tag_id)
        print(tag.name)

Querying Unsubscribe Lists

from ser_admin_api import SERClient
from ser_admin_api.suppression import UnsubscribeListQuery

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    lists = client.list_management.lists.unsubscribe.retrieve(
        UnsubscribeListQuery(page=1, size=100)
    )

    for unsubscribe_list in lists.data:
        print(unsubscribe_list.list_id)
        print(unsubscribe_list.name)

Querying Reporting Data

from datetime import date, timedelta

from ser_admin_api import SERClient
from ser_admin_api.reporting.failures import ReportInterval
from ser_admin_api.reporting.usage import UsageMetricsRequest, UsageTrafficSummaryQuery, UsageTrendRequest

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    end = date.today()
    start = end - timedelta(days=7)

    traffic = client.reporting.usage.traffic_summary.retrieve(
        UsageTrafficSummaryQuery().with_dates(gte=start, lte=end)
    )
    print(traffic.data.accepted_messages)

    relay_usage = client.reporting.usage.relay_users.retrieve(
        UsageMetricsRequest().with_dates(gte=start, lte=end).with_page(1, 100)
    )
    for row in relay_usage.data:
        print(row.relay_user_id)
        print(row.total_messages)

    trend = client.reporting.usage.message_trend.retrieve(
        UsageTrendRequest().with_dates(gte=start, lte=end).with_interval(ReportInterval.DAY)
    )
    print(trend.data)

Pagination

List-style resources return PagedResponse[T]. The first page is available immediately through result.page, and result.data is a shortcut for result.page.data. Iterating the result yields page objects, starting with the first page and then fetching continuation pages as needed. Calling items() flattens page boundaries and yields typed rows.

This works for both GET-backed and POST-backed paged SER endpoints. The wrapper keeps the original request method and request body/query object with the paged result, so continuation pages use the same endpoint shape as the first call.

from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient("<enter_your_principal_here>", "<enter_your_secret_here>")

    result = client.relay.relay_users.retrieve()
    print("First Page Number: {}".format(result.page.current_page_number))
    for relay_user in result.data:
        print(relay_user.name)

    for page in result:
        print("Current Page Number: {}".format(page.current_page_number))
        print("Page Size: {}".format(page.page_size))
        print("Total Records: {}".format(page.record_count))
        for relay_user in page:
            print(relay_user.name)
        break

    for relay_user in client.relay.relay_users.retrieve().items():
        print(relay_user.name)

Common paged resource actions include:

client.relay.verified_domains.retrieve()
client.relay.relay_users.retrieve()
client.relay.relay_users.search.retrieve(...)
client.connector_config.connectors.retrieve()
client.connector_config.connectors.search.retrieve(...)
client.connector_config.connectors.details.retrieve()
client.connector_config.connectors.details.search.retrieve(...)
client.tag_management.tags.retrieve()
client.tag_management.tags["tag-id"].notes.retrieve()
client.list_management.lists.unsubscribe.retrieve()
client.list_management.lists.unsubscribe["list-id"].addresses.retrieve(...)
client.list_management.lists.unsubscribe["list-id"].relay_users.retrieve(...)
client.list_management.lists.unsubscribe.requests.retrieve(...)
client.reporting.usage.relay_users.retrieve(...)
client.reporting.usage.tags.retrieve(...)
client.reporting.failures.relay_users.retrieve(...)
client.reporting.failures.tags.retrieve(...)

Creating Or Updating Resources

Mutation request bodies are typed request objects. Constructor arguments model required or common fields. Builder methods add optional fields while keeping the final JSON body aligned with the SER API.

from ser_admin_api.relay_users import RelayUserCreate

create_request = (
    RelayUserCreate("cluster-23", "Example Relay User")
    .with_allowed_address(mail_from="example.com", header_from="example.com")
    .generate(length=16, allow_lowercase=True, allow_uppercase=True)
    .with_tag("tag-id")
)

print(create_request.to_mapping())
from ser_admin_api.relay_users import RelayUserAllowedAddress, RelayUserUpdate

relay_user = client.relay.relay_users["relay-user-id"].retrieve().data

update_request = RelayUserUpdate(
    allowed_address=[
        RelayUserAllowedAddress(
            mail_from=address.mail_from,
            header_from=address.header_from,
        )
        for address in relay_user.allowed_addresses
    ],
).with_allowed_address(
    mail_from="new.example.com",
    header_from="new.example.com",
)

print(update_request.to_mapping())
from ser_admin_api.connectors import ConnectorCreate

create_request = (
    ConnectorCreate(name="Example Connector", port=587, region="US")
    .with_allowed_ip("192.0.2.10")
)

print(create_request.to_mapping())

Relay-user address configuration can also be patched incrementally. This is useful when you want to add or remove allowed address pairs or sender rewrite rules without sending a full relay-user update.

from ser_admin_api.relay_users import AddressConfigPatch

patch_request = (
    AddressConfigPatch()
    .add_allowed_address("new.example.com", "new.example.com")
    .remove_allowed_address("old.example.com", "old.example.com")
)

result = client.relay.relay_users["relay-user-id"].address_config.patch(patch_request)

print(result.data.allowed_addresses)
print(result.add_failed.allowed_addresses)

Connector and relay-user create/update/status calls modify tenant configuration. The examples show the request shapes without changing existing tenant data.

Typed Response Objects

Responses expose quick access to status, reason, URL, the decoded payload, and the native requests response through the underlying Klarient response object. SER rows are modeled as typed dictionary wrappers: known fields have Pythonic properties, and newly-added API fields remain available through dictionary-style access.

connector = client.connector_config.connectors["connector-id"].retrieve()

print(connector.status)
print(connector.data.connector_id)
print(connector.data.name)

# New or endpoint-specific fields remain available from the underlying mapping.
print(connector.data.get("newFieldFromApi"))

Network Options

Network settings such as proxy, timeout, and SSL verification are configured with RequestsOptions.

from klarient import RequestsOptions, RequestsTimeout
from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient(
        "<enter_your_principal_here>",
        "<enter_your_secret_here>",
        options=RequestsOptions(
            timeout=RequestsTimeout(connect=10, read=600),
            proxy="http://proxy.example.com:3128",
            verify_ssl=True,
        ),
    )

Proxy Support

A single proxy URL is used for both HTTP and HTTPS requests.

SOCKS5 proxy example:

from klarient import RequestsOptions
from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient(
        "<enter_your_principal_here>",
        "<enter_your_secret_here>",
        options=RequestsOptions(
            proxy="socks5h://proxyuser:proxypass@proxy.example.com:8128",
        ),
    )

HTTP proxy example:

from klarient import RequestsOptions
from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient(
        "<enter_your_principal_here>",
        "<enter_your_secret_here>",
        options=RequestsOptions(
            proxy="http://proxyuser:proxypass@proxy.example.com:8080",
        ),
    )

If your environment requires different proxies by scheme, pass a requests-style mapping:

from klarient import RequestsOptions
from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient(
        "<enter_your_principal_here>",
        "<enter_your_secret_here>",
        options=RequestsOptions(
            proxy={
                "http": "http://proxy.example.com:8080",
                "https": "http://secure-proxy.example.com:8080",
            },
        ),
    )

If your environment already uses standard proxy variables, those can also be used.

export HTTP_PROXY="http://proxy.example.com:8080"
export HTTPS_PROXY="http://proxy.example.com:8080"

HTTP Timeout Settings

from klarient import RequestsOptions, RequestsTimeout
from ser_admin_api import SERClient

if __name__ == '__main__':
    client = SERClient(
        "<enter_your_principal_here>",
        "<enter_your_secret_here>",
        options=RequestsOptions(timeout=RequestsTimeout(connect=10, read=600)),
    )

Endpoint Coverage

The wrapper models the reviewed SER API areas:

  • Reporting usage v1 and v2 overview
  • Reporting failures
  • Relay configuration and relay users
  • Connector configuration
  • Tag management
  • List management unsubscribe and unsubscribe requests

Metadata

Release files for ser-admin-api 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ser-admin-api 0.3.0
File Size Uploaded Provenance
ser_admin_api-0.3.0.tar.gz 67.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ser-admin-api 0.3.0
File Interpreter ABI Platform
ser_admin_api-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 148.0 kB

Release files / ser_admin_api-0.3.0.tar.gz

Download URL ser_admin_api-0.3.0.tar.gz
Size 67.7 kB
Tags Source
SHA-256 checksum
How to use checksums
67de5d2cc1d9a5b00c471fa5cbfe687a42e15f5082d7154f25ab147ea0d6e9b1
BLAKE2b-256 checksum
How to use checksums
928b79eac0163f351ce6b63e63c0a101d74347415d22c749c9a6aa92da85ce3f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 Jul 24, 2026.

Transparency log

Release files / ser_admin_api-0.3.0-py3-none-any.whl

Download URL ser_admin_api-0.3.0-py3-none-any.whl
Size 80.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1433aab2d0cde01bfea05207ad97784350e5036399c1f1ad3e7557f7c91f1d5b
BLAKE2b-256 checksum
How to use checksums
31fa120a2957fd34e3f2717ea6f9de6d5a34298ad0ceaf501b70540e9fe45cc6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 Jul 24, 2026.

Transparency log

Security for ser-admin-api 0.3.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.

Select from 2 files

Attestation for ser_admin_api-0.3.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 Jul 24, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page