The xmlrpc.client module calls remote procedures exposed by an XML-RPC server. The protocol represents methods, parameters, and results as XML and uses HTTP or HTTPS for transport. It still appears in legacy systems, appliances, publishing platforms, and enterprise integrations that must be maintained without third-party dependencies.
XML-RPC is simple, but it is not a modern default for new public APIs. Python’s documentation warns that the client is not secure against maliciously constructed XML data. Use it only with authenticated, trusted endpoints, enable TLS, enforce timeouts and response limits, and keep an explicit allowlist of remote methods.
Your first ServerProxy call
from xmlrpc.client import ServerProxy
with ServerProxy(
"https://rpc.example.com/RPC2",
use_builtin_types=True,
) as proxy:
result = proxy.calculator.add(7, 5)
print(result)
ServerProxy creates a dynamic proxy. Accessing proxy.calculator.add does not make a request yet; the call happens when arguments are supplied. The complete dotted method name is sent to the server.
Use the proxy as a context manager so the transport is closed. Do not construct a method name directly from untrusted text, because that can expose operations the application did not intend to call.
Important options
In addition to the URI, ServerProxy accepts options such as:
allow_none=Trueto enable the common extension forNone.use_builtin_types=Trueto return dates asdatetimeand binary values asbytes.headersfor additional HTTP headers.contextfor anssl.SSLContexton HTTPS connections.transportto customize connections, timeouts, proxies, or observability.
Not every server accepts None. Enable the extension only when the service contract explicitly supports it.
Supported data types
XML-RPC has a smaller type system than Python. Common values include booleans, bounded integers, floating-point numbers, strings, arrays, string-keyed structs, dates, and binary values.
payload = {
"id": 42,
"active": True,
"tags": ["python", "rpc"],
"price": 19.90,
}
with ServerProxy(URL, use_builtin_types=True) as proxy:
response = proxy.catalog.create(payload)
Struct keys must be strings. Sets, generators, arbitrary complex objects, and many custom classes cannot be transmitted directly. Convert domain objects into a simple documented contract before calling the service.
Integer limits
Traditional XML-RPC integers are signed 32-bit values. Some implementations accept extensions such as i8 or biginteger, but interoperability varies. Large identifiers should be negotiated in the contract and are often safer as strings.
def rpc_id(value: int) -> str:
if value < 0:
raise ValueError("invalid identifier")
return str(value)
Never assume that two XML-RPC products implement the same numeric extensions.
Dates and use_builtin_types
With use_builtin_types=True, received dates can become datetime.datetime objects. Without it, values may arrive as the module’s DateTime wrapper.
from datetime import datetime, timezone
from xmlrpc.client import ServerProxy
now = datetime.now(timezone.utc).replace(tzinfo=None)
with ServerProxy(URL, use_builtin_types=True) as proxy:
proxy.events.record("backup", now)
XML-RPC does not carry rich timezone information. Define whether all timestamps are UTC and avoid ambiguous local times.
Binary data
Bytes are encoded as Base64. With use_builtin_types=True, returned data may be bytes. Otherwise, access Binary.data.
from xmlrpc.client import Binary, ServerProxy
content = b"binary data"
with ServerProxy(URL, use_builtin_types=True) as proxy:
result = proxy.files.upload(Binary(content))
Base64 increases transfer size. Avoid moving large files through XML-RPC unless the service has strict limits and explicit support. A dedicated upload endpoint or object store is usually better.
Fault: a remote application error
When the server executes the request but returns an XML-RPC fault, the client raises Fault. It exposes faultCode and faultString.
from xmlrpc.client import Fault, ServerProxy
try:
with ServerProxy(URL) as proxy:
proxy.users.get(999)
except Fault as error:
print("Remote code:", error.faultCode)
print("Message:", error.faultString)
Do not render faultString directly as HTML or treat it as a structured trusted value. It may disclose internals or include externally influenced text.
ProtocolError: an HTTP-layer failure
ProtocolError represents HTTP or HTTPS problems such as 401, 403, 404, or 500 before a valid XML-RPC response is available. It includes the URL, status code, message, and headers.
from xmlrpc.client import ProtocolError
try:
proxy.system.status()
except ProtocolError as error:
print(error.errcode, error.errmsg)
A remote application fault and a transport response require different actions. Do not retry invalid credentials or a missing endpoint indefinitely. For the standard HTTP client’s hierarchy, read Python urllib.error.
Other transport failures
DNS, timeout, connection refusal, and TLS can surface as OSError, TimeoutError, ssl.SSLError, or transport-specific exceptions. Keep the try block small and preserve the original cause.
try:
result = proxy.system.status()
except Fault:
raise
except ProtocolError:
raise
except OSError as error:
raise RuntimeError("RPC service unavailable") from error
HTTPS certificate validation
For HTTPS URIs, current Python versions verify the certificate and hostname by default. Supply a context when using a corporate CA or an explicit minimum TLS version.
import ssl
from xmlrpc.client import ServerProxy
context = ssl.create_default_context(cafile="corporate-ca.pem")
context.minimum_version = ssl.TLSVersion.TLSv1_2
proxy = ServerProxy(
"https://rpc.example.com/RPC2",
context=context,
use_builtin_types=True,
)
Never use an unverified context as a permanent workaround. The guide to Python ssl explains trust chains, hostname verification, and mTLS.
Authentication
The module accepts Basic Authentication credentials embedded in the URL, but that form can leak through logs, history, diagnostics, and monitoring. Prefer controlled headers or a custom transport and always use HTTPS.
headers = (("Authorization", "Bearer SHORT_LIVED_TOKEN"),)
proxy = ServerProxy(URL, headers=headers)
Do not hard-code tokens. Load secrets from the environment or a secret manager, rotate them, and redact the header everywhere.
Adding a timeout with a custom transport
ServerProxy has no direct timeout argument. A custom transport can create bounded connections.
import http.client
import xmlrpc.client
class TimeoutTransport(xmlrpc.client.SafeTransport):
def __init__(self, timeout: float = 10.0, context=None):
super().__init__(context=context)
self.timeout = timeout
def make_connection(self, host):
return http.client.HTTPSConnection(
host,
timeout=self.timeout,
context=self.context,
)
Test custom transports against the exact Python version used in production because internal details can evolve. For low-level HTTP controls, read Python http.client.
Introspection
Some servers expose system.listMethods(), system.methodHelp(), and system.methodSignature().
with ServerProxy(URL) as proxy:
methods = proxy.system.listMethods()
for name in methods:
print(name)
Introspection helps development and diagnostics, but it can reveal sensitive surface area. It is not authorization. The client should maintain its own allowlist.
MultiCall
MultiCall batches several calls into one system.multicall request when the server supports it.
from xmlrpc.client import MultiCall, ServerProxy
with ServerProxy(URL) as proxy:
batch = MultiCall(proxy)
batch.catalog.get(1)
batch.catalog.get(2)
results = list(batch())
Batching reduces network round trips but increases request cost and response size. Limit item count, define partial-failure behavior, and avoid grouping destructive operations without clear semantics.
XML security
The official documentation warns about malicious XML. Even a known endpoint can be compromised and return resource-intensive content. Enforce limits at a reverse proxy, set timeouts, authenticate both sides, and segment the network.
Never expose an XML-RPC client directly to an arbitrary URL supplied by a user. That combines SSRF, hostile XML, and credential-leakage risks.
Logging and observability
Log the logical method name, duration, summarized outcome, failure code, and correlation identifier. Do not log full parameters, binary results, passwords, tokens, or personal data.
Because proxy methods are dynamic, wrap them in explicit domain functions. This improves validation, metrics, and testability.
Recommended tests
Test supported types, None enabled and disabled, out-of-range integers, dates, binary values, Fault, ProtocolError, timeout, invalid certificates, oversized responses, missing methods, partial multicall failures, and log redaction.
Run integration tests against a controlled local server. The companion guide to xmlrpc.server shows how to create one.
When not to use XML-RPC
For a new API, JSON over HTTP, gRPC, or another modern protocol usually offers a stronger ecosystem for authentication, schemas, streaming, and observability. XML-RPC mainly makes sense when interoperating with an existing system.
Conclusion
xmlrpc.client simplifies legacy XML-RPC integrations through ServerProxy. Use it safely by limiting methods and types, validating TLS, protecting credentials, configuring timeouts, separating Fault from ProtocolError, and treating all returned XML as untrusted.
Read the official xmlrpc.client documentation and the XML-RPC specification. For new systems, evaluate alternatives; for legacy systems, hide the protocol behind a well-tested domain adapter.







