Skip to main content

pmt4api — Python for wmt4api

pmt4api is the complete Python interface to wmt4api, the MetaTrader 4 client library from mtapi.one. The protocol, licensing and trading logic stay in the licensed core (wmt4api.dll), which this package loads in-process; every public capability of the C# library is available here under a Pythonic name, with plain data types, a small error hierarchy, context managers, and sync, async and callback styles.

No MetaTrader terminal is needed — the core talks to your broker directly. It is the sibling of pmt5api (MT5): the two share their conventions, so a system that trades both reads alike.

Parity with the C# library is measured, not claimed: pmt4api.COVERAGE maps every public member of the C# MT4API and OrderClient classes to its Python counterpart (or states why it has none), and the test suite fails if the core gains a member that is not accounted for. pmt4api/docs/FROM-CSHARP.md (inside the installed package) is that table; pmt4api/docs/API.md (inside the installed package; python -m pmt4api prints its folder) is the reference.

Install

pip install pmt4api

pip chooses the wheel for your platform. The Linux x86-64 wheel carries everything — the core and a .NET runtime — so there is nothing else to install. On any other platform the py3-none-any wheel carries the core and needs a .NET 8 (or later) runtime on the machine (dotnet --list-runtimes should list Microsoft.NETCore.App). From a downloaded file: pip install pmt4api-<version>-py3-none-<platform>.whl.

Windows (x64). Install the .NET 8 runtime once (winget install Microsoft.DotNet.Runtime.8, or the installer from https://dotnet.microsoft.com/download/dotnet/8.0), then pip install pmt4api. Check it with python -m pmt4api: it prints the versions, finds the runtime and constructs a client offline. Set the key in the shell that runs your program — PowerShell: $env:WMT4API_KEY = "wk_...", cmd: set WMT4API_KEY=wk_.... A string the broker sent can hold characters your console's code page cannot print: set PYTHONUTF8=1 (or run chcp 65001) to avoid a UnicodeEncodeError.

Windows (x64). Use the py3-none-any wheel from PyPI — pip install pmt4api picks it — and install the .NET 8 runtime once (winget install Microsoft.DotNet.Runtime.8, or the installer from https://dotnet.microsoft.com/download/dotnet/8.0); dotnet --list-runtimes must list Microsoft.NETCore.App 8.x or later. Check the install with python -m pmt4api: it prints the versions, finds the runtime and constructs a client offline. Set the key in the shell that runs your program — PowerShell: $env:WMT4API_KEY = "wk_...", cmd: set WMT4API_KEY=wk_.... A library string can hold characters your console code page cannot print: set PYTHONUTF8=1 (or run chcp 65001) to avoid a UnicodeEncodeError when you print what the broker sent.

You also need your API key (wk_…, the same key you use with wmt4api):

export WMT4API_KEY=wk_...         # or pass api_key=... to Client(...)
Python 3.9 or newer
Platform Linux x86-64 verified (glibc 2.28 or newer). Windows and macOS are not verified yet — see Known limitations
Optional pip install pandas for to_frame

Quick start

from pmt4api import Client

with Client(login=12345678, password="investor-password", host="broker-access-host", port=443) as c:
    acct = c.account()
    print(acct.name, acct.balance, acct.currency, "equity", acct.equity)

    sym = c.find_symbol("EURUSD")                  # brokers add suffixes: EURUSDm, EURUSD.s ...
    print(c.quote(sym))                            # Quote(symbol='EURUSDm', bid=..., ask=..., time=...)

    for p in c.positions():
        print(p.ticket, p.symbol, p.order_type, p.lots, p.profit)

Use an investor (read-only) password for anything that does not trade.

Market data

from datetime import datetime
import pmt4api

c.symbols()                       # dict[str, SymbolInfo]: digits, contract size, min/max/step lots, execution ...
c.find_symbols("XAU")             # every name that starts with it: ['XAUUSDm', 'XAUEURm', ...]
c.symbol("XAUUSDm")               # one symbol's specification

h1 = c.bars("EURUSDm", "H1", count=500)                    # the latest 500 H1 bars, oldest first
d1 = c.bars("XAUUSDm", "D1", start=datetime(2026, 1, 1))   # everything since a date the server still holds
df = pmt4api.to_frame(h1)                                  # pandas DataFrame indexed by time

Timeframes: M1 M5 M15 M30 H1 H4 D1 W1 MN1 — the nine the MT4 server serves; it delivers each itself. A series holds completed bars: the bar that is still forming is not part of what the server sends (read the live price with quote()), and end= reads the series up to that moment with no look-ahead. The broker limits how far back it goes (M1 is typically a few days), so a long request returns what exists; one request carries at most 32 767 bars and longer requests page backwards for you. volume is the tick volume.

MT4 and MT5 differ here. The MT4 package returns completed bars only and excludes a bar that opens at end=; on MT5 end= includes the bar stamped exactly end and the bar still forming. Code that runs on both has to drop the forming MT5 bar itself.

Live quotes

with Client(...) as c:
    for q in c.quotes("EURUSDm", "XAUUSDm", timeout=30):   # ends after 30 s without a tick
        print(q.symbol, q.bid, q.ask, q.time)
async with Client(...) as c:                               # connects without blocking the event loop
    async with c.aquotes("EURUSDm") as quotes:             # attached on entry, detached on exit
        async for q in quotes:
            print(q)
remove = c.on_quote(lambda q: print(q))                    # callback style
c.subscribe("EURUSDm")
...
remove()

Until you subscribe to something the server streams a default set of a few hundred symbols; the moment you do it streams only what you subscribed. The core keeps quotes honest across that change: quote() and the tick-value calculations subscribe a symbol they need and wait for a tick that arrives after the request instead of using a price cached from the feed that stopped (if no tick comes — a closed market — the last price is returned, and Quote.time says how old it is). A position's symbol is put on the feed as soon as the position exists and stays there, because the position's profit and the account's equity are priced from it. account() reports priced_from, the server time of the oldest tick its figures used: minutes old while the market is open means the feed is not delivering, and the equity should not be trusted.

Callbacks are delivered by one dispatcher thread per client, one at a time, in the order the core handed the events over — you need no locks between callbacks, and a slow callback does not stall the connection. (The core raises each event on a thread-pool thread, so two events that happen within microseconds of each other can swap; order updates of one ticket carry Order.profit and times you can compare.) If a callback falls more than 20,000 events behind, the oldest events are dropped, a warning is logged and c.events_dropped counts them. An exception raised inside a callback is logged (logging.getLogger("pmt4api")) and swallowed. The iterators attach when you create them, drop the oldest item when the consumer falls behind, and detach on close() / async with / garbage collection.

Trading

from datetime import timedelta

o = c.buy("EURUSDm", 0.10, sl=1.0950, tp=1.1100, comment="bot-1")   # market order at the ask; sell() at the bid
c.modify(o.ticket, sl=1.0970)                     # change stops (returns the order as the server holds it)
c.close(o.ticket, 0.05)                           # close half (returns the closing record); close(ticket) closes it all

p = c.place_order("EURUSDm", "buy_stop", 0.10, 1.1200, sl=1.1150,
                  expiration=timedelta(days=2))   # also buy_limit sell_limit sell_stop; a datetime works too
c.modify(p.ticket, price=1.1210)                  # a pending order's price; its expiration is kept
c.cancel(p.ticket)                                # returns the order that was deleted

c.close_by(a.ticket, b.ticket)                    # close one position against an opposite one (hedged accounts)
c.close_all_by("EURUSDm")                         # ... every opposite pair of the symbol

lots must be a multiple of 0.01 (the MT4 wire carries hundredths of a lot); a finer volume is refused before anything is sent. slippage is in points. For an instant-execution symbol the order goes at price (default: the current ask or bid) and a move of more than slippage points raises RequoteError with the prices the server now offers; a market-execution symbol ignores both and fills at its own price.

Tagging orders: use comment, not magic. The core accepts a magic number but does not transmit it, so an order cannot carry one. Rather than let a strategy believe its orders are tagged, magic= other than 0 raises InvalidRequestError. A comment (ASCII, at most 31 characters) comes back on Order.comment; Order.magic is still read from orders that carry one placed elsewhere.

Without waiting — a request id now, the outcome through events:

c.on_order_progress(lambda p: print(p.request_id, p.stage, p.order and p.order.ticket))   # InProcess, Accepted, Opened ...
rid = c.buy_async("EURUSDm", 0.10)
for p in c.order_progress(request_id=rid, timeout=15):   # or follow one request as an iterator
    print(p.stage)

In asyncio — the synchronous calls on a worker thread: await c.abuy(...), asell, aplace_order, amodify, aclose, acancel, aclose_by, aclose_all_by, aquote, abars, ahistory. Cancelling the await of a trade (a wait_for timeout, a cancelled task) does not recall a request that is already on its way: the order may still be sent, so check orders() afterwards. Cancelling ahistory does abandon the download.

Test on a demo account first. Nothing here is ever retried or resent for you: if the server does not confirm in time the call raises RequestTimeout and the order may have been accepted — look at orders() before sending again. NoQuoteError is different: no price was available, so nothing was sent. A market order needs a price, so even the *_async calls first wait (up to quote_timeout) for the symbol's first quote. After a partial close the remainder of the position may carry a new ticket: read positions().

Sizing and risk

r = c.lots_for_risk("EURUSDm", entry_price=1.1000, stop_price=1.0950, risk_amount=50.0, side="buy")
for w in r.warnings:
    print("warning:", w)                  # ok can still be True with a caveat (the broker's minimum volume risks more)
if r.ok:
    c.buy("EURUSDm", r.lots, sl=1.0950)
else:
    print("not sizing:", r.reason)

The value of a price unit comes from tick_value() (converted through the account currency's cross rates), the broker's volume step, minimum and maximum from the account's own group; a size that cannot be justified is refused, not guessed, and the volume is rounded down. Amounts are in the currency the account reports; there is no cent-account scaling, so check the first result against your terminal. Also: required_margin(symbol, lots), order_profit(symbol, open, close, lots), tick_value(symbol, side), contract_size(symbol).

Account, history and curves

Call Returns
account(refresh=True) AccountInfo — balance, credit, equity, floating profit, margin, free margin, margin level, leverage, currency, type
balance, equity, margin, free_margin, margin_level, profit the same figures as cheap reads from the cache (no refresh; the margin may still wait for a conversion pair's first tick)
orders() / positions() / pending_orders() / order(ticket) list[Order] working / open positions / pending / one
history(start, end) list[Order] — closed orders and balance / credit entries, by close time
summary(start, end) AccountSummary — realized profit, deposits, withdrawals
equity_history(start, "D1") list[EquityPoint] — balance (exact) and equity (reconstructed) curve with drawdowns

MT4 keeps one record for open positions, pending orders, closed orders and ledger entries: Order.order_type tells them apart (Buy / Sell, BuyLimit … , Balance / Credit), and is_market, is_pending, is_capital_flow say it directly. Times are naive datetime values in the broker's server time; an unset time is None.

History is asked for by window. history() and summary() take a start and an end, and one call covers at most max_history_days (366 by default; set it on the client, 0 removes the limit); equity_history() runs from its start to now, so its start is at most that long ago. A missing, reversed or longer window raises InvalidRequestError before anything is sent. For a longer period, ask window by window and add the results up:

from datetime import timedelta

end = c.server_time                                   # server time; "up to now"
closed = c.history(end - timedelta(days=30), end)
year = c.summary(end - timedelta(days=365), end)

history(), summary() and equity_history() each open their own short-lived broker connection and cost one licence request, which the licence service rate-limits per minute: fetch once and reuse the result. history() takes a threading.Event as cancel= to abandon the wait. A window with no closed order in it gives a flat equity curve at the balance, never an empty one.

Events

on_quote, on_order_update, on_order_progress, on_connect, on_disconnect, on_quote_history, on_symbols_update — each returns a function that removes the callback. quotes(), order_updates() and order_progress() (and the a… async versions) turn a stream into an iterator. Events reach you in the order the server sent them — a ticket's open before its modifications and its close — and callbacks run one at a time on the client's own thread.

Staying connected

Pass auto_reconnect=True to let the core re-establish a dropped session (exponential backoff up to 60 s, failover across the primary server, servers=[(host, port), ...] and the access servers the broker published, re-subscription). Watch on_disconnect (its error says why: ConnectionLostError, and None when you closed the client) and on_connect. A session that carries nothing for no_message_timeout (default 30 s; the server answers a ping every few seconds) is declared dead and closed, so a half-open link is noticed instead of lingering. A refusal retrying cannot cure (an expired licence, a rejected login) stops the reconnecting. Do not poll connected and call connect() in a loop — brokers rate-limit logins that hammer their access servers. reconnect() re-establishes by hand and returns whether it worked. After a reconnect, re-query account() and orders(); subscriptions are restored, cached snapshots are not. disconnect() is final: it also stops a reconnect that is in flight.

Configuration

Setting Argument Environment variable
API key api_key= WMT4API_KEY (then WMTAPI_KEY)
Licence service URL (leave at its default) token_url= WMT4API_TOKEN_URL
Core library (only if you do not use a wheel that carries it) dll_path= WMT4API_DLL
Timeouts (seconds, each > 0) connect_timeout (one attempt on one server; default 6), token_timeout (10), trade_timeout (30; ≥ 1), quote_timeout (5) / history_timeout (30) / bars_timeout (30) — the defaults of quote, history, bars, no_message_timeout (30; ≥ 10, or 0 for off) —
Reconnect auto_reconnect, reconnect_backoff (3, doubling up to reconnect_max_backoff, 30), reconnect_max_attempts (0 = no limit), servers= —
Orders placed_type= (manual, expert, dealer …), comment_encoding= (default latin-1) —
History window max_history_days= (366; 0 = no limit) — also a client attribute —
.NET runtime location — DOTNET_ROOT

A core path you give (argument or WMT4API_DLL) is used exactly as given and is an error if it does not exist. The .NET runtime is taken from DOTNET_ROOT if you set it, else the runtime bundled in the wheel, else an installed one. The bundled runtime runs in invariant-culture mode on machines without ICU. The MT4 core has no proxy support.

These two settings decide what code runs in your process. WMT4API_DLL (or dll_path=) names the library that is loaded, and DOTNET_ROOT (or, without it, the dotnet found on PATH) names the .NET runtime that is started; both run with your rights. Set them only from configuration you control, and do not run with an environment someone else can change. With enable_core_logging() switched on, the package logs once when either was chosen by the environment. The licence service URL must be https (plain http only for a loopback test service), and redirects from it are not followed.

Diagnostics

import logging
logging.basicConfig(level=logging.DEBUG)
pmt4api.enable_core_logging()          # the core's own messages -> logging.getLogger("pmt4api.core")

Offline helpers

load_servers_ini(path_or_bytes) / save_servers_ini(records) read and write a terminal's servers.ini. python -m pmt4api prints what is installed and whether it works here (--connect also reads an account, using PMT4_LOGIN, PMT4_PASSWORD, PMT4_HOST — a bare host, or host:port, or the port in PMT4_PORT); the output holds no secrets.

What a closed order says about why

An MT4 server pushes the same PositionClose update for a stop-loss, a take-profit and a close you asked for; the reason is in the order's comment, which the server suffixes with [sl] or [tp] for a stop or a take-profit hit. Pressing Ctrl+C while connect() is dialling is honoured when the dial ends (up to connect_timeout), not at once. token_timeout bounds one licence request; a failed request is tried again and a connect tries each server of the list, so a connect to an unreachable licence service can wait several times that (about 44 s at the default 10).

Errors

Everything the core raises reaches you as one of these, all subclasses of pmt4api.WmtError; the original exception stays available as __cause__.

Exception Meaning
LicenseError The licence service refused the key. .reason is one of NoKey, Invalid, Expired, Revoked, RateLimited, LoginNotAllowed, AccountLimitReached, Unknown; .status is its HTTP status.
TokenServiceError The licence service could not be reached or answered unusably — retry, don't re-prompt for a key.
LoginRejectedError The broker rejected the account number or password (.code is usually INVALID_ACCOUNT, 65). A ServerError.
ConnectError No access server could be reached (or none answered within connect_timeout).
ConnectionLostError An established connection dropped; with a trade in flight, whether it was applied is unknown.
ServerError The broker refused a request. .code is the MT4 return code by name (NOT_ENOUGH_MONEY, INVALID_STOPS, TRADE_DISABLED …) and .code_value its number (pmt4api.TRADE_CODES).
RequoteError An instant-execution price moved: .bid / .ask are the prices now offered. A ServerError; nothing was filled.
InvestorModeError A read-only (investor) login tried to trade. Nothing was sent. A ServerError.
InvalidRequestError The request was rejected before it was sent, e.g. an unknown symbol or a magic number (also a ValueError).
BusyError A bar download for the same symbol and timeframe is already running.
RequestTimeout No answer in time (also a built-in TimeoutError). For a trade call the request was sent: the order may have been accepted.
NoQuoteError No quote arrived in time to read or to trade against. Nothing was sent.
RequestCancelled You cancelled the wait (history(cancel=...)).
NotConnectedError The call needs a connection and the client is not connected.
ProtocolError The server sent something the core could not decode.
CoreNotFoundError / RuntimeNotFoundError wmt4api.dll or the .NET runtime was not found; the message says where it looked.

Calls made through client.raw bypass this translation and raise the core's own exceptions.

from pmt4api import Client, LicenseError, LoginRejectedError, RequoteError, TokenServiceError

try:
    c = Client(...).connect()
except LicenseError as e:
    print("licence refused:", e.reason)
except LoginRejectedError as e:
    print("the broker rejected the login:", e.code)
except TokenServiceError as e:
    print("licence service unreachable:", e)

What has been verified

Run against real demo accounts (not only unit-tested), through a licence authority of its own, from clean containers with no .NET installed, on three brokers (Exness, ICMarkets, DPrime — all market-execution): connect, licensing errors (bad key, unreachable licence service, wrong password, unreachable host); account, symbols and trading rules; quotes and streams, including that a quote stays current after the feed is narrowed; bars on all nine timeframes (identical to the core's own download), paging and windows; history, summary and an equity curve reconciled against the history rows; sizing; order entry — market, pending with expiration, modify, partial close, close-by, close-all-by, cancel, asynchronous with progress events — with the server's own rejections arriving as typed errors; failing over from a dead primary to a backup server; dropping the connection twice mid-stream, a broker that refuses connections for a while and a link that goes silent without closing — each recovered with subscriptions restored — a manual reconnect, and a disconnect() that stays final; concurrent subscribing, quoting, bar and account reads from several threads without an error; and every shipped example.

Two unattended runs on demo accounts: three hours (5 symbols, 212 333 ticks, the account read every minute, 17 bar and 17 history downloads; the connection cut three times — recovered in 3.7–4.0 s — and made silent twice — noticed at the 20 s setting, back after 24 s) with no error, no event dropped, resident memory flat (~147 MB, −0.7 MB between the halves), threads and file descriptors flat — on a build of the core that preceded the last review fixes; and a ninety-minute run of the released wheel in a container (DPrime demo): 88 account reads, 8 bar and 8 history downloads, 17 far-away pending orders placed and cancelled, 7 real positions opened, checked against the feed and closed (10 more market orders were refused by the broker with TRADE_DISABLED, which the run counts as skipped), the connection cut four times (recovered in about 3 s) and made silent twice (noticed at the 20 s setting, back in under 24 s), which finished with no error and no growth check tripped (resident memory 104 MB at one minute, 140 MB at half an hour, 145 MB at the hour; threads 19–21, file descriptors 100).

Known limitations

  • Linux x86-64 is the verified platform. Windows, macOS, arm64 and musl-based Linux have not been verified.
  • No magic number: the core does not transmit it (see Trading). Tag orders with comment.
  • Investor-mode refusal (InvestorModeError, and is_investor / can_trade reading the account record's read-only flag) and requotes (RequoteError) are covered by unit tests; they have not been provoked live (the available demo accounts are market-execution and no investor password was at hand).
  • Comments are ASCII, at most 31 characters: orders are read back through an ASCII parser.
  • history(), summary() and equity_history() use the primary server only: each opens its own short connection to host:port and does not fail over to servers=.
  • The connect handshake and the first data load give up after 30 s whatever connect_timeout says (that one bounds the TCP dial of each server); a server that stalls there is skipped and the next one tried.
  • trade_timeout counts whole seconds (the core truncates it). A ServerError with code TRADE_TIMEOUT (128) is the server's own timeout while processing: the outcome is not known — look at orders() before sending again.
  • The server's UTC offset is read once per session; a session that spans the broker's daylight-saving change has server_time, bars(end=now) and a relative expiration off by an hour until it reconnects.
  • The silence watchdog's premise — the server answers each ping within seconds — was measured on a quiet session (one answer per ping); a market-closed weekend has not been observed. no_message_timeout=0 switches it off.
  • The server lists a default feed of its own until you subscribe; is_subscribed() reports your subscriptions, and quote() answers for any symbol either way.
  • One Client is one broker connection. Its methods can be called from several threads, but a trading call is neither queued nor serialized for you; two threads acting on the same ticket is your responsibility.
  • TradeTimeout below one second makes the core give up straight after sending, so trade_timeout must be ≥ 1.
  • A folder named wmt4api in your working directory is handled automatically (it would otherwise hide the core's .NET namespace).

Licensing and security

Your connection is authorised per session by the hosted licence service, using your API key; the core talks to your broker directly. What is sent to the licence service and what is not, and the security notes, are in the core's own documents, which travel with the wheel: pmt4api/_native/docs/LICENSING.md, SECURITY.md and ERRORS.md (and the core's CHANGELOG.md next to them). python -m pmt4api prints where they are.

Third-party components

The Linux wheel includes the Microsoft .NET runtime (MIT licence; its notices are in pmt4api/_dotnet/) and the wmt4api core with its own notices (pmt4api/_native/THIRD-PARTY-NOTICES.txt).

Trademarks

MetaTrader, MT4 and MT5 are trademarks of MetaQuotes Ltd. wmt4api/wmt5api/pmt4api/pmt5api are independent products of Sen Trading (mtapi.one); they are not affiliated with, endorsed by or sponsored by MetaQuotes Ltd. All other names are the property of their respective owners.

Licence and support

See LICENSE.txt. Use of the core library is governed by the wmt4api commercial SDK licence. For keys, support and licence terms contact mtapi.one.

Metadata

Release files for pmt4api 1.2.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 pmt4api 1.2.0
File Size Uploaded Provenance Actions
pmt4api-1.2.0.tar.gz 74.9 kB Without provenance Details for pmt4api-1.2.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for pmt4api 1.2.0
File Interpreter ABI Platform Actions
pmt4api-1.2.0-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details for pmt4api-1.2.0-py3-none-manylinux_2_28_x86_64.whl
pmt4api-1.2.0-py3-none-any.whl Python 3 none any Details for pmt4api-1.2.0-py3-none-any.whl

Total release size: 33.9 MB

Release files / pmt4api-1.2.0.tar.gz

Download URL pmt4api-1.2.0.tar.gz
Size 74.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d2ede962dc98136137a476f513d20097fa88e43c0534059f72c7b8897ab60af8
BLAKE2b-256 checksum
How to use checksums
a8b44110b0b6e03461ce8c9352ec51365858527e57eeaf737bb8c86a61885261
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / pmt4api-1.2.0-py3-none-manylinux_2_28_x86_64.whl

Download URL pmt4api-1.2.0-py3-none-manylinux_2_28_x86_64.whl
Size 33.7 MB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
be3e2573e240b7bccac1d925741565747ef3721bf9bb0d705f850678c6cea29c
BLAKE2b-256 checksum
How to use checksums
cd5bc21ab9256c7cece1943bf115c84cb1996c0c7025d0cf92b02f31a2fb53af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / pmt4api-1.2.0-py3-none-any.whl

Download URL pmt4api-1.2.0-py3-none-any.whl
Size 149.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30f8a51fd87a37cd56600c4edf5dc04170614a0b5169cabbe2765065c39c2ba5
BLAKE2b-256 checksum
How to use checksums
89640b2616705a103cb219f0b37cc10eff0325b4394b00f48bfad298ce22bc7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Security for pmt4api 1.2.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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

Release history Release notifications | RSS feed

This release

1.2.0 This release

3 release files No provenance

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