# Build
Source: https://iso8583sim.com/docs/api-reference/messages/build
/rest/openapi.json post /build
Build a raw message from an MTI and field values.
# Convert
Source: https://iso8583sim.com/docs/api-reference/messages/convert
/rest/openapi.json post /convert
Convert a message to another ISO 8583 version, listing dropped fields and notes.
# Explain
Source: https://iso8583sim.com/docs/api-reference/messages/explain
/rest/openapi.json post /explain
Explain a message in plain English, with rules (default) or an LLM.
# Generate
Source: https://iso8583sim.com/docs/api-reference/messages/generate
/rest/openapi.json post /generate
Generate a valid test message from options, or from a description with an LLM.
# Parse
Source: https://iso8583sim.com/docs/api-reference/messages/parse
/rest/openapi.json post /parse
Parse a raw message into its MTI, bitmap and named fields.
# Validate
Source: https://iso8583sim.com/docs/api-reference/messages/validate
/rest/openapi.json post /validate
Validate a message, optionally against other networks' rules too.
A message that fails to parse is reported as invalid (200), not as an error.
# Health
Source: https://iso8583sim.com/docs/api-reference/meta/health
/rest/openapi.json get /health
Liveness check.
# Builder
Source: https://iso8583sim.com/docs/api/core/builder
iso8583sim.core.builder
`iso8583sim.core.builder`
## `ISO8583Builder`
```python theme={null}
ISO8583Builder(version: ISO8583Version = ISO8583Version.V1987)
```
Builder for creating ISO 8583 messages with network support
### `build()`
```python theme={null}
def build(message: ISO8583Message) -> str
```
Build raw ISO 8583 message string from message object
### `build_bytes()`
```python theme={null}
def build_bytes(message: ISO8583Message, wire_format: WireFormat | None = None) -> bytes
```
Build a message as bytes in a wire format (binary bitmap, BCD, EBCDIC...).
Defaults to ASCII with a binary bitmap. See iso8583sim.wire.WireFormat for the presets.
### `build_with_mac()`
```python theme={null}
def build_with_mac(
message: ISO8583Message,
key: str | bytes,
algorithm: int = 3,
padding: int = 1,
) -> str
```
Build a message and fill in its MAC (field 64, or 128 with a secondary bitmap).
Requires the security extra. See iso8583sim.security.sign\_message.
### `create_message()`
```python theme={null}
def create_message(mti: str, fields: dict[int, str]) -> ISO8583Message
```
Create an ISO8583Message object with validation
### `create_response()`
```python theme={null}
def create_response(
request: ISO8583Message,
response_fields: dict[int, str],
) -> ISO8583Message
```
Create a response message based on a request message
### `create_reversal()`
```python theme={null}
def create_reversal(
original: ISO8583Message,
additional_fields: dict[int, str] | None = None,
) -> ISO8583Message
```
Create a reversal message
### `create_network_management_message()`
```python theme={null}
def create_network_management_message(
message_type: str,
network: CardNetwork | None = None,
) -> ISO8583Message
```
Create a network management message
### `build_emv_data()`
```python theme={null}
def build_emv_data(emv_tags: dict[str, str]) -> str
```
Build EMV data field (field 55) from tags
# Codes
Source: https://iso8583sim.com/docs/api/core/codes
Reference tables and lookups for ISO 8583 code values.
`iso8583sim.core.codes`
## Functions
### `detect_network_from_pan()`
```python theme={null}
def detect_network_from_pan(pan: str) -> CardNetwork | None
```
Detect the card network from a PAN's issuer identification prefix.
**Parameters**
Primary Account Number (digits only)
The matching CardNetwork, or None if the prefix is not recognised
# Convert
Source: https://iso8583sim.com/docs/api/core/convert
Convert messages between ISO 8583 versions (1987, 1993, 2003).
`iso8583sim.core.convert`
## `ConversionResult`
```python theme={null}
ConversionResult(
message: ISO8583Message,
dropped: dict[int, str] = dict(),
notes: list[str] = list(),
)
```
Outcome of a version conversion.
### `lossless`
True when every field was carried over.
## Functions
### `convert_mti()`
```python theme={null}
def convert_mti(mti: str, target: ISO8583Version) -> str
```
Rewrite the MTI's version digit for the target version.
### `convert_message()`
```python theme={null}
def convert_message(message: ISO8583Message, target: ISO8583Version) -> ConversionResult
```
Convert a message to another ISO 8583 version.
The MTI version digit is rewritten, original data elements move between field 90 and
field 56 where the versions differ, and every other field is checked against the
target version's definition. Fields that don't fit are dropped and reported rather
than truncated, because a shortened value would be silently wrong.
**Parameters**
Parsed message in its source version
Version to convert to
ConversionResult with the converted message, dropped fields and notes
# Describe
Source: https://iso8583sim.com/docs/api/core/describe
Rule-based, human-readable descriptions of ISO 8583 messages.
`iso8583sim.core.describe`
These helpers need no LLM. They power `iso8583sim explain --no-llm` and the
MCP server's explain\_message tool.
## Functions
### `field_name()`
```python theme={null}
def field_name(
field_number: int,
network: CardNetwork | None,
version: ISO8583Version,
) -> str
```
Return a field's description, or "Unknown" if it is not defined.
### `field_entries()`
```python theme={null}
def field_entries(message: ISO8583Message) -> list[dict[str, Any]]
```
List a message's fields (excluding the MTI) as number, name and value entries.
### `mask_pan()`
```python theme={null}
def mask_pan(pan: str) -> str
```
Mask a PAN, keeping the first six and last four digits.
### `format_amount()`
```python theme={null}
def format_amount(amount: str, currency: str | None) -> str
```
Format a minor-unit amount (field 4) using the currency's exponent, e.g. "10.00 USD".
### `decode_emv_tags()`
```python theme={null}
def decode_emv_tags(data: str) -> list[dict[str, Any]]
```
Decode EMV TLV data into named tags, explaining the TVR (95) and CID (9F27).
### `describe_mti()`
```python theme={null}
def describe_mti(mti: str) -> dict[str, str]
```
Describe an MTI's message class, function and origin.
### `describe_message()`
```python theme={null}
def describe_message(message: ISO8583Message) -> dict[str, Any]
```
Describe a message in plain language without an LLM.
**Parameters**
Parsed message
Dictionary with a one-paragraph "summary", the MTI breakdown, named fields
and, when field 55 is present, decoded EMV tags
# EMV
Source: https://iso8583sim.com/docs/api/core/emv
EMV (Europay, Mastercard, Visa) TLV data handling.
`iso8583sim.core.emv`
This module provides functions for parsing and building EMV Tag-Length-Value
encoded data, commonly found in ISO 8583 Field 55.
## Functions
### `parse_emv_data()`
```python theme={null}
def parse_emv_data(data: str) -> dict[str, str]
```
Parse EMV TLV data into a dictionary of tags and values.
**Parameters**
Hex-encoded EMV TLV data
Dictionary mapping tag strings to value strings (hex-encoded)
**Raises**
* `ValueError`: If data is malformed
### `build_emv_data()`
```python theme={null}
def build_emv_data(tags: dict[str, str]) -> str
```
Build EMV TLV data from a dictionary of tags and values.
**Parameters**
Dictionary mapping tag strings to value strings (hex-encoded)
Hex-encoded EMV TLV data string
### `get_tag_name()`
```python theme={null}
def get_tag_name(tag: str) -> str
```
Get the description of an EMV tag.
**Parameters**
Tag identifier (hex string)
Description of the tag, or "Unknown" if not found
### `explain_tvr()`
```python theme={null}
def explain_tvr(tvr_hex: str) -> list[str]
```
Explain Terminal Verification Results.
**Parameters**
5-byte TVR as hex string (10 characters)
List of issues/flags that are set
### `explain_cid()`
```python theme={null}
def explain_cid(cid_hex: str) -> str
```
Explain Cryptogram Information Data.
**Parameters**
CID byte as hex string
Description of the cryptogram type
# Network rules
Source: https://iso8583sim.com/docs/api/core/network_rules
Network-specific field format rules.
`iso8583sim.core.network_rules`
Only rules that can be traced to a published description of the field are included, so a
valid message is never rejected by a guessed format. Sources:
* Field 22 (POS entry mode): the DE022 code reference aligned to the Mastercard Customer
Interface Specification (Feb 2024) and Visa VisaNet Authorization-Only Online Messages
(Oct 2023), [https://docs.tech.sofi.com/pro/reference/api-reference-de022-codes](https://docs.tech.sofi.com/pro/reference/api-reference-de022-codes)
* Mastercard field 48: the Mastercard Customer Interface Specification layout of DE 48,
a transaction category code followed by subelements (ID n-2, length n-2, data).
## Functions
### `check_pos_entry_mode()`
```python theme={null}
def check_pos_entry_mode(value: str, network: CardNetwork) -> list[str]
```
Check field 22 against the codes the network defines.
### `check_mastercard_de48()`
```python theme={null}
def check_mastercard_de48(value: str) -> list[str]
```
Check Mastercard field 48: a transaction category code, then well-formed subelements.
### `check_network_fields()`
```python theme={null}
def check_network_fields(fields: dict[int, str], network: CardNetwork) -> list[str]
```
Apply the network's field rules to the fields that are present.
# Parser
Source: https://iso8583sim.com/docs/api/core/parser
iso8583sim.core.parser
`iso8583sim.core.parser`
## `EMVTag`
```python theme={null}
EMVTag(tag: str, length: int, value: str, raw: str)
```
EMV Tag data structure
## `ISO8583Parser`
```python theme={null}
ISO8583Parser(
version: ISO8583Version = ISO8583Version.V1987,
pool: MessagePool | None = None,
)
```
Parser for ISO 8583 messages with network support
Initialize the parser.
**Parameters**
ISO8583 version to use
Optional MessagePool for object reuse in high-throughput scenarios
### `parse()`
```python theme={null}
def parse(message: str, network: CardNetwork | None = None) -> ISO8583Message
```
Parse an ISO 8583 message string into an ISO8583Message object
### `parse_bytes()`
```python theme={null}
def parse_bytes(
data: bytes,
wire_format: WireFormat | None = None,
network: CardNetwork | None = None,
) -> ISO8583Message
```
Parse a message received as bytes in a wire format (binary bitmap, BCD, EBCDIC...).
Defaults to ASCII with a binary bitmap. See iso8583sim.wire.WireFormat for the presets.
### `parse_file()`
```python theme={null}
def parse_file(filename: str) -> list[ISO8583Message]
```
Parse multiple messages from file
# Pool
Source: https://iso8583sim.com/docs/api/core/pool
Object pooling for high-throughput ISO8583 message processing.
`iso8583sim.core.pool`
## `MessagePool`
```python theme={null}
MessagePool(size: int = 100)
```
Thread-safe object pool for ISO8583Message instances.
Using a pool reduces object allocation overhead in high-throughput
scenarios by reusing message objects instead of creating new ones.
pool = MessagePool(size=100)
# Get a message from the pool
msg = pool.acquire("0100", \{2: "4111111111111111"})
# Process the message...
# Return it to the pool when done
pool.release(msg)
Note: Messages returned to the pool have their fields cleared,
so don't hold references to field data after releasing.
Initialize the message pool.
**Parameters**
Maximum number of messages to keep in the pool
### `acquire()`
```python theme={null}
def acquire(
mti: str,
fields: dict[int, str],
version: ISO8583Version = ISO8583Version.V1987,
network: CardNetwork | None = None,
raw_message: str | None = None,
bitmap: str | None = None,
) -> ISO8583Message
```
Get a message from the pool or create a new one.
**Parameters**
Message Type Indicator
Message fields dictionary
ISO8583 version
Card network
Original raw message string
Message bitmap
An ISO8583Message instance (either recycled or new)
### `release()`
```python theme={null}
def release(msg: ISO8583Message) -> None
```
Return a message to the pool for reuse.
**Parameters**
The message to return to the pool
Note: The message's fields are cleared to avoid holding
references to old data.
### `clear()`
```python theme={null}
def clear() -> None
```
Clear all messages from the pool.
### `size`
Get current number of messages in the pool.
### `max_size`
Get maximum pool size.
## Functions
### `get_default_pool()`
```python theme={null}
def get_default_pool(size: int = 100) -> MessagePool
```
Get or create the default global message pool.
**Parameters**
Pool size (only used on first call)
The default MessagePool instance
### `reset_default_pool()`
```python theme={null}
def reset_default_pool() -> None
```
Reset the default global pool.
# Samples
Source: https://iso8583sim.com/docs/api/core/samples
Valid sample messages for tests and demos.
`iso8583sim.core.samples`
## Functions
### `sample_message()`
```python theme={null}
def sample_message(
message_type: str = 'auth',
network: CardNetwork | None = None,
pan: str | None = None,
amount_minor_units: int = 1000,
currency: str = '840',
stan: str = '123456',
) -> ISO8583Message
```
Create a valid 1987 test message.
**Parameters**
"auth" (0100), "financial" (0200) or "echo" (0800)
Card network. Detected from the PAN when omitted.
Card number. A well-known test PAN for the network is used when omitted.
Amount in minor units (1000 = 10.00)
ISO 4217 numeric currency code
System trace audit number
The message, ready to build
# Types
Source: https://iso8583sim.com/docs/api/core/types
iso8583sim.core.types
`iso8583sim.core.types`
## `ISO8583Version`
Bases: `Enum`
ISO 8583 protocol versions
| Member | Value |
| - | - |
\| `V1987` | `'1987'` |
\| `V1993` | `'1993'` |
\| `V2003` | `'2003'` |
## `FieldType`
Bases: `Enum`
Field data types in ISO 8583
| Member | Value |
| - | - |
\| `NUMERIC` | `'n'` |
\| `ALPHA` | `'a'` |
\| `ALPHANUMERIC` | `'an'` |
\| `BINARY` | `'b'` |
\| `SPECIAL` | `'s'` |
\| `TRACK2` | `'z'` |
\| `LLVAR` | `'ll'` |
\| `LLLVAR` | `'lll'` |
## `CardNetwork`
Bases: `Enum`
Card network identifiers
| Member | Value |
| - | - |
\| `VISA` | `'VISA'` |
\| `MASTERCARD` | `'MASTERCARD'` |
\| `AMEX` | `'AMEX'` |
\| `DISCOVER` | `'DISCOVER'` |
\| `JCB` | `'JCB'` |
\| `UNIONPAY` | `'UNIONPAY'` |
## `MessageClass`
Bases: `Enum`
Message class identifiers (position 2 of MTI)
| Member | Value |
| - | - |
\| `AUTHORIZATION` | `'1'` |
\| `FINANCIAL` | `'2'` |
\| `FILE_ACTIONS` | `'3'` |
\| `REVERSAL` | `'4'` |
\| `RECONCILIATION` | `'5'` |
\| `ADMINISTRATIVE` | `'6'` |
\| `FEE_COLLECTION` | `'7'` |
\| `NETWORK_MANAGEMENT` | `'8'` |
## `MessageFunction`
Bases: `Enum`
Message function identifiers (position 3 of MTI)
| Member | Value |
| - | - |
\| `REQUEST` | `'0'` |
\| `RESPONSE` | `'1'` |
\| `ADVICE` | `'2'` |
\| `ADVICE_RESPONSE` | `'3'` |
\| `NOTIFICATION` | `'4'` |
\| `NETWORK_REQUEST` | `'8'` |
\| `NETWORK_RESPONSE` | `'9'` |
## `MessageOrigin`
Bases: `Enum`
Message origin identifiers (position 4 of MTI)
| Member | Value |
| - | - |
\| `ACQUIRER` | `'0'` |
\| `ACQUIRER_REPEAT` | `'1'` |
\| `ISSUER` | `'2'` |
\| `ISSUER_REPEAT` | `'3'` |
\| `OTHER` | `'4'` |
\| `OTHER_REPEAT` | `'5'` |
## `FieldDefinition`
```python theme={null}
FieldDefinition(
field_type: FieldType,
max_length: int,
description: str,
field_number: int | None = None,
encoding: str = 'ascii',
min_length: int | None = None,
padding_char: str | None = None,
padding_direction: str = 'left',
)
```
Definition of an ISO 8583 field
## `ISO8583Message`
```python theme={null}
ISO8583Message(
mti: str,
fields: dict[int, str],
version: ISO8583Version = ISO8583Version.V1987,
network: CardNetwork | None = None,
raw_message: str | None = None,
bitmap: str | None = None,
)
```
Represents a complete ISO 8583 message
### `message_class`
Get message class from MTI
### `message_function`
Get message function from MTI
### `message_origin`
Get message origin from MTI
## `ISO8583Error`
Bases: `Exception`
Base exception for ISO 8583 errors
## `ParseError`
Bases: `ISO8583Error`
Raised when parsing fails
## `ValidationError`
Bases: `ISO8583Error`
Raised when validation fails
## `BuildError`
Bases: `ISO8583Error`
Raised when message building fails
## Functions
### `get_field_definition()`
```python theme={null}
def get_field_definition(
field_number: int,
network: CardNetwork | None = None,
version: ISO8583Version = ISO8583Version.V1987,
) -> FieldDefinition | None
```
Get field definition considering network and version specifics.
**Parameters**
The field number to look up
Optional card network for network-specific definitions
ISO8583 version
FieldDefinition if found, None otherwise
Priority order:
1. Network-specific definition
2. Version-specific definition
3. Standard field definition
Note: Results are cached for performance (lru\_cache with 512 entries).
### `is_valid_mti()`
```python theme={null}
def is_valid_mti(mti: str) -> bool
```
Check if MTI is valid.
**Parameters**
Message Type Indicator string
True if MTI is valid, False otherwise
Validates:
* Length is 4 digits
* All characters are numeric
* Each position contains valid values
### `get_network_required_fields()`
```python theme={null}
def get_network_required_fields(network: CardNetwork) -> list[int]
```
Get list of required fields for a specific network.
**Parameters**
Card network
List of required field numbers
### `get_field_format_pattern()`
```python theme={null}
def get_field_format_pattern(network: CardNetwork, field_number: int) -> str | None
```
Get network-specific field format pattern.
**Parameters**
Card network
Field number
Regex pattern string if exists, None otherwise
### `is_binary_field()`
```python theme={null}
def is_binary_field(field_def: FieldDefinition) -> bool
```
Check if field is binary type.
**Parameters**
Field definition
True if field is binary, False otherwise
# Validator
Source: https://iso8583sim.com/docs/api/core/validator
iso8583sim.core.validator
`iso8583sim.core.validator`
## `ISO8583Validator`
```python theme={null}
ISO8583Validator()
```
Enhanced validator for ISO 8583 messages with network support
### `validate_field()`
```python theme={null}
def validate_field(
field_number: int,
value: str,
field_def: FieldDefinition,
network: CardNetwork | None = None,
) -> tuple[bool, str | None]
```
Validate field value
### `validate_message()`
```python theme={null}
def validate_message(message: ISO8583Message) -> list[str]
```
Validate complete ISO 8583 message
### `validate_processing_code()`
```python theme={null}
def validate_processing_code(code: str) -> bool
```
Validate processing code format
### `validate_mti()`
```python theme={null}
def validate_mti(mti: str) -> tuple[bool, str | None]
```
Validate Message Type Indicator
**Parameters**
4-digit MTI string
(is\_valid, error\_message)
### `validate_bitmap()`
```python theme={null}
def validate_bitmap(bitmap: str) -> tuple[bool, str | None]
```
Validate bitmap format and content
**Parameters**
Hexadecimal bitmap string
(is\_valid, error\_message)
### `validate_pan()`
```python theme={null}
def validate_pan(pan: str) -> bool
```
Validate Primary Account Number using Luhn algorithm
**Parameters**
Card number string
True if valid, False otherwise
### `validate_for_network()`
```python theme={null}
def validate_for_network(message: ISO8583Message, network: CardNetwork) -> list[str]
```
Validate a message as if it were sent on another network.
The message is not changed. Useful to check whether a message built for one
network also meets another network's required fields and formats.
### `validate_for_networks()`
```python theme={null}
def validate_for_networks(
message: ISO8583Message,
networks: list[CardNetwork] | None = None,
) -> dict[CardNetwork, list[str]]
```
Validate a message against several networks at once (all networks by default).
### `verify_mac()`
```python theme={null}
def verify_mac(
raw_message: str,
key: str | bytes,
algorithm: int = 3,
padding: int = 1,
) -> bool
```
Check the MAC in field 64 or 128 of a raw message. Requires the security extra.
### `validate_network_compliance()`
```python theme={null}
def validate_network_compliance(message: ISO8583Message) -> list[str]
```
Validate network-specific requirements
### `validate_emv_data()`
```python theme={null}
def validate_emv_data(emv_data: str) -> list[str]
```
Validate EMV data format (TLV structure)
### `validate_field_compatibility()`
```python theme={null}
def validate_field_compatibility(
field_number: int,
value: str,
version: ISO8583Version,
) -> list[str]
```
Validate field compatibility with ISO version
**Parameters**
Field number to validate
Field value
ISO8583 version
List of compatibility errors
# Demo helpers
Source: https://iso8583sim.com/docs/api/demo
Demo module with helper functions for notebooks and interactive use.
`iso8583sim.demo`
This module provides convenient functions for:
* Pretty-printing ISO 8583 messages
* Generating sample messages for different scenarios
* Explaining message components
## Functions
### `pretty_print()`
```python theme={null}
def pretty_print(message: ISO8583Message | str, show_raw: bool = False) -> None
```
Pretty print an ISO 8583 message.
**Parameters**
ISO8583Message object or raw message string
Whether to show the raw message bytes
### `explain_field()`
```python theme={null}
def explain_field(field_number: int, value: str | None = None) -> None
```
Explain a specific field's definition and optionally its value.
**Parameters**
The field number to explain
Optional field value to interpret
### `generate_auth_request()`
```python theme={null}
def generate_auth_request(
pan: str = '4111111111111111',
amount: int = 1000,
stan: str = '123456',
terminal_id: str = 'TERM0001',
merchant_id: str = 'MERCHANT123456 ',
network: CardNetwork | None = None,
) -> ISO8583Message
```
Generate a sample authorization request.
**Parameters**
Primary Account Number
Transaction amount in cents
System Trace Audit Number
Terminal ID (8 chars)
Merchant ID (15 chars)
Optional network type
ISO8583Message object
### `generate_financial_request()`
```python theme={null}
def generate_financial_request(
pan: str = '4111111111111111',
amount: int = 5000,
stan: str = '654321',
processing_code: str = '000000',
) -> ISO8583Message
```
Generate a sample financial request (0200).
**Parameters**
Primary Account Number
Transaction amount in cents
System Trace Audit Number
Processing code (default: purchase)
ISO8583Message object
### `generate_reversal()`
```python theme={null}
def generate_reversal(
original: ISO8583Message,
new_stan: str = '999999',
) -> ISO8583Message
```
Generate a reversal message for an original transaction.
**Parameters**
The original transaction message
New STAN for the reversal
ISO8583Message reversal
### `generate_network_message()`
```python theme={null}
def generate_network_message(message_type: str = 'echo') -> ISO8583Message
```
Generate a network management message.
**Parameters**
Type of message - 'echo', 'signon', 'signoff', 'key\_exchange'
ISO8583Message object
### `generate_emv_auth()`
```python theme={null}
def generate_emv_auth(
pan: str = '4111111111111111',
amount: int = 10000,
cryptogram: str = 'AABBCCDD11223344',
) -> ISO8583Message
```
Generate an EMV chip card authorization.
**Parameters**
Primary Account Number
Transaction amount in cents
Application cryptogram (8 bytes hex)
ISO8583Message with EMV data
### `build_and_parse()`
```python theme={null}
def build_and_parse(message: ISO8583Message) -> ISO8583Message
```
Build a message to raw format and parse it back.
Useful for testing roundtrip.
**Parameters**
ISO8583Message to process
Parsed message
### `validate()`
```python theme={null}
def validate(message: ISO8583Message | str) -> None
```
Validate a message and print results.
**Parameters**
ISO8583Message or raw message string
### `explain_emv()`
```python theme={null}
def explain_emv(emv_hex: str) -> None
```
Parse and explain EMV data.
**Parameters**
Hex-encoded EMV TLV data
# Base
Source: https://iso8583sim.com/docs/api/llm/base
Base classes for LLM providers.
`iso8583sim.llm.base`
This module defines the abstract base class for LLM providers, allowing
multiple backend implementations (Anthropic, OpenAI, Google, Ollama).
## `LLMResponse`
```python theme={null}
LLMResponse(
content: str,
model: str,
provider: str,
usage: dict[str, int] | None = None,
)
```
Response from an LLM provider.
## `LLMError`
Bases: `Exception`
Base exception for LLM-related errors.
## `ProviderNotAvailableError`
Bases: `LLMError`
```python theme={null}
ProviderNotAvailableError(provider: str, package: str)
```
Raised when a provider's dependencies are not installed.
## `ProviderConfigError`
Bases: `LLMError`
Raised when provider configuration is invalid.
## `GenerationError`
Bases: `LLMError`
Raised when message generation fails.
## `LLMProvider`
Bases: `ABC`
Abstract base class for LLM providers.
All LLM providers must implement this interface to be compatible
with MessageExplainer and MessageGenerator.
**Example**
```python theme={null}
class MyProvider(LLMProvider):
def complete(self, prompt, system=None):
return "response"
@property
def name(self):
return "my-provider"
```
### `complete()`
```python theme={null}
def complete(prompt: str, system: str | None = None) -> str
```
Send a prompt to the LLM and return the response text.
**Parameters**
The user prompt to send
Optional system prompt for context
The LLM's response text
**Raises**
* `LLMError`: If the API call fails
### `name`
Return the provider name for logging/display.
### `model`
Return the model name being used.
### `complete_with_metadata()`
```python theme={null}
def complete_with_metadata(prompt: str, system: str | None = None) -> LLMResponse
```
Send a prompt and return response with metadata.
Default implementation wraps complete(). Providers can override
for more detailed metadata.
**Parameters**
The user prompt to send
Optional system prompt for context
LLMResponse with content and metadata
# Explainer
Source: https://iso8583sim.com/docs/api/llm/explainer
Message Explainer using LLM to provide human-readable explanations.
`iso8583sim.llm.explainer`
## `MessageExplainer`
```python theme={null}
MessageExplainer(provider: LLMProvider | str | None = None)
```
Explains ISO 8583 messages in plain English using an LLM.
This class uses an LLM provider to generate human-readable explanations
of ISO 8583 messages, making them easier to understand for developers
and analysts.
**Example**
```python theme={null}
explainer = MessageExplainer() # Auto-detects provider
message = parser.parse("0100...")
print(explainer.explain(message))
```
"This is a VISA authorization request for \$100.00..."
```python theme={null}
# Or explain a raw message string
print(explainer.explain("0100702406C120E09000..."))
```
```python theme={null}
# Use a specific provider
explainer = MessageExplainer(provider="anthropic")
```
Initialize the MessageExplainer.
**Parameters**
LLM provider to use. Can be:
* LLMProvider instance: Use directly
* str: Provider name ('anthropic', 'openai', 'google', 'ollama')
* None: Auto-detect available provider
### `provider`
Return the LLM provider being used.
### `explain()`
```python theme={null}
def explain(message: ISO8583Message | str, verbose: bool = False) -> str
```
Explain an ISO 8583 message in plain English.
**Parameters**
ISO8583Message object or raw message string to explain
If True, include more technical details
Human-readable explanation of the message
### `explain_field()`
```python theme={null}
def explain_field(field_number: int, value: str) -> str
```
Explain a specific ISO 8583 field value.
**Parameters**
The field number (e.g., 2, 39, 55)
The field value to explain
Human-readable explanation of the field and its value
### `explain_error()`
```python theme={null}
def explain_error(error: str, message: ISO8583Message | str) -> str
```
Explain a validation or parsing error in context.
**Parameters**
The error message to explain
The message that caused the error
Human-readable explanation of the error and how to fix it
### `explain_response_code()`
```python theme={null}
def explain_response_code(code: str) -> str
```
Explain an ISO 8583 response code.
**Parameters**
The response code (e.g., "00", "51", "05")
Human-readable explanation of the response code
# Generator
Source: https://iso8583sim.com/docs/api/llm/generator
Message Generator using LLM to create ISO 8583 messages from natural language.
`iso8583sim.llm.generator`
## `MessageGenerator`
```python theme={null}
MessageGenerator(provider: LLMProvider | str | None = None)
```
Generates ISO 8583 messages from natural language descriptions.
This class uses an LLM to interpret natural language descriptions
and generate valid ISO 8583 messages.
**Example**
```python theme={null}
generator = MessageGenerator()
message = generator.generate("$100 VISA purchase at a gas station")
print(message.mti) # "0100"
print(message.fields[4]) # "000000010000"
```
```python theme={null}
# Generate without validation
message = generator.generate("refund $50", validate=False)
```
Initialize the MessageGenerator.
**Parameters**
LLM provider to use. Can be:
* LLMProvider instance: Use directly
* str: Provider name ('anthropic', 'openai', 'google', 'ollama')
* None: Auto-detect available provider
### `provider`
Return the LLM provider being used.
### `generate()`
```python theme={null}
def generate(description: str, validate: bool = True) -> ISO8583Message
```
Generate an ISO 8583 message from a natural language description.
**Parameters**
Natural language description of the desired message
(e.g., "\$100 VISA purchase at a gas station in NYC")
Whether to validate the generated message
Valid ISO8583Message object
**Raises**
* `GenerationError`: If the message cannot be generated or validated
### `suggest_fields()`
```python theme={null}
def suggest_fields(partial_message: ISO8583Message) -> dict[int, str]
```
Suggest missing fields for a partial message.
**Parameters**
Partial ISO8583Message with some fields populated
Dictionary of suggested field values
# Providers
Source: https://iso8583sim.com/docs/api/llm/providers
LLM provider factory and auto-detection.
`iso8583sim.llm.providers`
This module provides a factory function to create LLM providers
and auto-detect available providers based on installed packages
and configured API keys.
## Functions
### `get_provider()`
```python theme={null}
def get_provider(name: str | None = None, **kwargs: Any) -> LLMProvider
```
Get an LLM provider instance.
If name is provided, creates that specific provider.
If name is None, auto-detects the first available provider.
**Parameters**
Provider name ('anthropic', 'openai', 'google', 'ollama')
or None for auto-detection
Additional arguments passed to the provider constructor
An initialized LLMProvider instance
**Raises**
* `ProviderConfigError`: If no provider is available or configured
**Example**
```python theme={null}
provider = get_provider() # Auto-detect
provider = get_provider("anthropic") # Specific provider
provider = get_provider("openai", model="gpt-6-luna")
```
### `list_available_providers()`
```python theme={null}
def list_available_providers() -> list[str]
```
List all available and configured providers.
List of provider names that are installed and configured
### `list_installed_providers()`
```python theme={null}
def list_installed_providers() -> list[str]
```
List all installed providers (may not be configured).
List of provider names that have their packages installed
# MCP server
Source: https://iso8583sim.com/docs/api/mcp/server
MCP server exposing the ISO 8583 toolkit to MCP clients (Claude, Cursor, etc.).
`iso8583sim.mcp.server`
## Functions
### `create_server()`
```python theme={null}
def create_server() -> MCPServer
```
Create the iso8583sim MCP server with its tools, resources and prompts.
### `main()`
```python theme={null}
def main() -> None
```
Run the MCP server over stdio.
# Client
Source: https://iso8583sim.com/docs/api/net/client
Asyncio TCP client that sends ISO 8583 requests and waits for their responses.
`iso8583sim.net.client`
## `ISO8583Client`
```python theme={null}
ISO8583Client(
host: str,
port: int,
wire_format: WireFormat | None = None,
framing: Framing | None = None,
timeout: float = 10.0,
ssl: ssl_module.SSLContext | bool | None = None,
version: ISO8583Version = ISO8583Version.V1987,
)
```
Send requests to an ISO 8583 host over TCP.
Responses are matched to requests by STAN (field 11), so several requests can be in
flight on one connection at once.
**Example**
```python theme={null}
async with ISO8583Client("127.0.0.1", 8583) as client:
response = await client.send(sample_message())
print(response.fields[39])
```
**Parameters**
Host name or address
TCP port
Byte layout of messages (defaults to ASCII with a binary bitmap)
Length header and optional TPDU (defaults to a 2-byte binary header)
Seconds to wait for a response
True or an SSLContext to connect over TLS
ISO 8583 version used to decode responses
### `connect()`
```python theme={null}
async def connect() -> None
```
Open the connection. send() also connects on demand.
### `close()`
```python theme={null}
async def close() -> None
```
Close the connection. Requests still waiting fail with ConnectionError.
### `send()`
```python theme={null}
async def send(message: ISO8583Message, timeout: float | None = None) -> ISO8583Message
```
Send a request and return the matching response.
**Parameters**
Request message. It must have a STAN (field 11).
Seconds to wait for the response (defaults to the client timeout)
The decoded response
**Raises**
* `TimeoutError`: No response arrived in time
* `ConnectionError`: The connection closed before the response arrived
# Framing
Source: https://iso8583sim.com/docs/api/net/framing
Message framing for TCP: length headers and TPDUs.
`iso8583sim.net.framing`
## `Framing`
```python theme={null}
Framing(
header: str = '2b',
tpdu: bytes | None = None,
header_includes_self: bool = False,
)
```
How messages are delimited on a TCP stream.
**Attributes**
Length header: "2b" / "4b" (2 or 4 byte big-endian binary) or "2a" / "4a"
(2 or 4 ASCII digits). The length covers everything after the header.
Optional TPDU sent after the length header, e.g. bytes.fromhex("6000010000").
On responses the destination and source addresses are swapped.
Some hosts count the header bytes in the length. Off by default.
### `frame()`
```python theme={null}
def frame(message: bytes, tpdu: bytes | None = None) -> bytes
```
Add the TPDU (if configured) and the length header to an encoded message.
### `read()`
```python theme={null}
async def read(reader: asyncio.StreamReader) -> tuple[bytes | None, bytes]
```
Read one framed message. Returns (tpdu, message).
Raises asyncio.IncompleteReadError if the connection closes mid-frame, and EOFError
when it closes cleanly between frames.
### `split()`
```python theme={null}
def split(buffer: bytes) -> tuple[list[tuple[bytes | None, bytes]], bytes]
```
Split a byte buffer into complete frames. Returns (frames, leftover bytes).
## Functions
### `swap_tpdu()`
```python theme={null}
def swap_tpdu(tpdu: bytes | None) -> bytes | None
```
Swap a TPDU's destination and source addresses, as a response does.
# Mock host
Source: https://iso8583sim.com/docs/api/net/host
Mock issuer host: an asyncio TCP server that answers ISO 8583 requests by rules.
`iso8583sim.net.host`
## `Rule`
```python theme={null}
Rule(
mti: list[str] = list(),
pan_prefix: list[str] = list(),
amount_gt: int | None = None,
amount_lt: int | None = None,
network: str | None = None,
fields: dict[int, str] = dict(),
respond: str = '00',
action: str = 'respond',
delay_ms: int = 0,
)
```
One response rule. The first rule whose conditions all match is used.
**Attributes**
Request MTIs this rule applies to (any when empty)
Only match cards whose PAN starts with one of these
Only match when field 4 is greater than this (minor units)
Only match when field 4 is less than this (minor units)
Only match this card network (e.g. "VISA")
Only match when these fields have exactly these values
Response code for field 39
"respond" (default), "drop" (never answer, to test timeouts) or
"close" (close the connection)
Wait this long before answering
### `from_dict()`
```python theme={null}
def from_dict(data: dict[str, Any]) -> Rule
```
Build a rule from a rules-file entry: \{"match": \{...}, "respond": "51", ...}.
### `matches()`
```python theme={null}
def matches(request: ISO8583Message) -> bool
```
## `HostStats`
```python theme={null}
HostStats(
received: int = 0,
responded: int = 0,
dropped: int = 0,
errors: int = 0,
response_codes: Counter[str] = Counter(),
)
```
Counters for what the mock host has seen and done.
## `MockHost`
```python theme={null}
MockHost(
rules: list[Rule] | None = None,
wire_format: WireFormat | None = None,
framing: Framing | None = None,
version: ISO8583Version = ISO8583Version.V1987,
)
```
A mock issuer host for testing acquirer and switch software.
**Example**
```python theme={null}
host = MockHost(rules=[Rule(amount_gt=100000, respond="51"), Rule()])
server = await host.start("127.0.0.1", 8583)
```
### `rule_for()`
```python theme={null}
def rule_for(request: ISO8583Message) -> Rule
```
### `build_response()`
```python theme={null}
def build_response(request: ISO8583Message, code: str) -> ISO8583Message
```
Build the response to a request with the given response code.
### `start()`
```python theme={null}
async def start(host: str = '127.0.0.1', port: int = 8583) -> asyncio.Server
```
Start listening. Returns the asyncio server (port 0 picks a free port).
### `stop()`
```python theme={null}
async def stop() -> None
```
### `port`
The port the host is listening on.
## Functions
### `load_rules()`
```python theme={null}
def load_rules(path: str | Path) -> list[Rule]
```
Load rules from a JSON or YAML file with a top-level "rules" list.
### `response_mti()`
```python theme={null}
def response_mti(mti: str) -> str
```
0100 -> 0110, 0200 -> 0210, 0400 -> 0410, 0800 -> 0810, 0120 -> 0130.
# Load testing
Source: https://iso8583sim.com/docs/api/net/load
Load generator: send many requests concurrently and measure throughput and latency.
`iso8583sim.net.load`
## `LoadReport`
```python theme={null}
LoadReport(
sent: int = 0,
responses: int = 0,
timeouts: int = 0,
errors: int = 0,
duration_s: float = 0.0,
latencies_ms: list[float] = list(),
response_codes: Counter[str] = Counter(),
)
```
Results of a load run. Latencies are in milliseconds.
### `throughput`
Responses per second.
### `percentile()`
```python theme={null}
def percentile(p: float) -> float
```
Latency percentile (nearest rank), e.g. percentile(95).
## Functions
### `run_load()`
```python theme={null}
async def run_load(
make_client: Callable[[], ISO8583Client],
count: int,
concurrency: int = 10,
connections: int = 1,
network: CardNetwork | None = None,
make_message: Callable[[int], ISO8583Message] | None = None,
) -> LoadReport
```
Send `count` requests with at most `concurrency` in flight, spread over `connections`.
**Parameters**
Creates a (not yet connected) client
Number of requests to send
Maximum requests waiting for a response at once
Number of TCP connections to spread requests over
Card network for the generated sample messages
Builds request i. Defaults to sample authorizations with unique STANs.
LoadReport with counts, response codes and latencies
# MACs
Source: https://iso8583sim.com/docs/api/security/mac
ISO 9797-1 message authentication codes for ISO 8583 fields 64 and 128.
`iso8583sim.security.mac`
For testing and simulation only. Production MACs are generated inside an HSM.
## Functions
### `generate_mac()`
```python theme={null}
def generate_mac(
data: str | bytes,
key: str | bytes,
algorithm: int = 3,
padding: int = 1,
length: int = 8,
) -> str
```
Generate an ISO 9797-1 MAC with DES based block ciphers.
Algorithm 1 is a CBC-MAC with the whole key (single, double or triple length).
Algorithm 3 is the retail MAC (ANSI X9.19): single DES CBC with the left key half,
then decrypt with the right half and encrypt with the left half on the final block.
**Parameters**
Data to authenticate. A str is encoded as ASCII, which is how this
simulator carries ISO 8583 messages.
MAC key as bytes or hex (8, 16 or 24 bytes for algorithm 1; 16 for algorithm 3)
ISO 9797-1 MAC algorithm: 1 or 3
ISO 9797-1 padding method: 1 or 2
MAC length in bytes, 4 to 8. Truncation keeps the leftmost bytes.
The MAC as uppercase hex
### `verify_mac()`
```python theme={null}
def verify_mac(
data: str | bytes,
mac: str,
key: str | bytes,
algorithm: int = 3,
padding: int = 1,
) -> bool
```
Check a MAC. The expected length is taken from the given MAC.
### `mac_field()`
```python theme={null}
def mac_field(fields: dict[int, str]) -> int
```
Return which field carries the MAC: 128 when the message has a secondary bitmap, else 64.
### `sign_message()`
```python theme={null}
def sign_message(
message: ISO8583Message,
key: str | bytes,
builder: ISO8583Builder | None = None,
algorithm: int = 3,
padding: int = 1,
) -> str
```
Build a message with its MAC in field 64 (or 128 with a secondary bitmap).
**Parameters**
Message to build. Any existing MAC field is replaced.
MAC key as bytes or hex
Builder to use (defaults to one for the message's version)
ISO 9797-1 MAC algorithm: 1 or 3
ISO 9797-1 padding method: 1 or 2
The raw message with the MAC filled in
### `verify_message()`
```python theme={null}
def verify_message(
raw: str,
key: str | bytes,
algorithm: int = 3,
padding: int = 1,
version: ISO8583Version | None = None,
) -> bool
```
Check the MAC in field 64 or 128 of a raw message built by this simulator.
# PIN blocks
Source: https://iso8583sim.com/docs/api/security/pinblock
ISO 9564-1 PIN blocks (formats 0, 1, 3 and 4).
`iso8583sim.security.pinblock`
For testing and simulation only. Real PIN handling happens inside an HSM, and clear PINs
and keys must never pass through application code like this.
## Functions
### `encode_pin_block()`
```python theme={null}
def encode_pin_block(pin: str, pan: str | None = None, fmt: int = 0) -> bytes
```
Build a clear (unencrypted) 8-byte PIN block in format 0, 1 or 3.
**Parameters**
4 to 12 digit PIN
Primary Account Number (required for formats 0 and 3)
PIN block format: 0, 1 or 3
The 8-byte clear PIN block
### `decode_pin_block()`
```python theme={null}
def decode_pin_block(block: bytes, pan: str | None = None, fmt: int = 0) -> str
```
Recover the PIN from a clear 8-byte PIN block in format 0, 1 or 3.
### `encrypt_pin_block()`
```python theme={null}
def encrypt_pin_block(pin: str, pan: str | None, key: str | bytes, fmt: int = 0) -> str
```
Build an encrypted PIN block as it appears in field 52.
Formats 0, 1 and 3 use a double or triple length TDES key and give 8 bytes. Format 4
uses an AES key and gives 16 bytes, which fits field 52 in the 1993 and 2003 versions.
**Parameters**
4 to 12 digit PIN
Primary Account Number (not used by format 1)
PIN encryption key as bytes or hex
PIN block format: 0, 1, 3 or 4
The encrypted PIN block as uppercase hex
### `decrypt_pin_block()`
```python theme={null}
def decrypt_pin_block(
block: str | bytes,
pan: str | None,
key: str | bytes,
fmt: int = 0,
) -> str
```
Recover the PIN from an encrypted PIN block (formats 0, 1, 3 and 4).
A wrong key is detected in every format, because it scrambles the whole block. A wrong
PAN is always detected in format 4, but only sometimes in formats 0 and 3: there the PAN
is applied with a single XOR, so a different PAN can still decode to a well-formed block
with a different PIN. This is a property of those formats, not of this implementation.
**Raises**
* `SecurityError`: The block doesn't decode to a valid PIN block
# REST app
Source: https://iso8583sim.com/docs/api/web/app
REST API for iso8583sim, built with FastAPI.
`iso8583sim.web.app`
Run with `iso8583sim web` or `uvicorn iso8583sim.web.app:app`. Interactive docs are served
at /docs and the OpenAPI schema at /openapi.json.
Set ISO8583SIM\_PUBLIC=1 to run it as a public demo: LLM features are turned off, so nobody
can spend the server's API keys, and browsers on any site may call it (CORS).
## `FieldValue`
Bases: `BaseModel`
## `MessageRequest`
Bases: `BaseModel`
## `ParseResponse`
Bases: `BaseModel`
## `BuildRequest`
Bases: `BaseModel`
## `BuildResponse`
Bases: `BaseModel`
## `ValidateRequest`
Bases: `MessageRequest`
## `NetworkResult`
Bases: `BaseModel`
## `ValidateResponse`
Bases: `BaseModel`
## `ExplainRequest`
Bases: `MessageRequest`
## `ExplainResponse`
Bases: `BaseModel`
## `GenerateRequest`
Bases: `BaseModel`
## `GenerateResponse`
Bases: `BaseModel`
## `ConvertRequest`
Bases: `MessageRequest`
## `ConvertResponse`
Bases: `BaseModel`
## `HealthResponse`
Bases: `BaseModel`
## Functions
### `create_app()`
```python theme={null}
def create_app(public: bool | None = None) -> FastAPI
```
Create the REST API application.
**Parameters**
Run as a public demo, with LLM features off and CORS open to any origin.
Defaults to the ISO8583SIM\_PUBLIC environment variable.
# Encode and decode
Source: https://iso8583sim.com/docs/api/wire/codec
Encode messages to bytes and decode them back, in any WireFormat.
`iso8583sim.wire.codec`
## Functions
### `encode_message()`
```python theme={null}
def encode_message(
message: ISO8583Message,
fmt: WireFormat | None = None,
builder: ISO8583Builder | None = None,
) -> bytes
```
Encode a message as bytes in the given wire format.
The message is validated and normalized exactly as ISO8583Builder.build() does, so
encoding with WireFormat.ascii\_hex() gives the same characters as build().
**Parameters**
Message to encode
Wire format (defaults to ascii\_binary)
Builder to use for validation (defaults to one for the message's version)
The encoded message
### `decode_message()`
```python theme={null}
def decode_message(
data: bytes,
fmt: WireFormat | None = None,
version: ISO8583Version = ISO8583Version.V1987,
network: CardNetwork | None = None,
) -> ISO8583Message
```
Decode bytes in the given wire format into a message.
Field values come back in this library's usual string form (binary fields as uppercase
hex, numbers as digit strings), so the result works with the validator, explainer,
converter and everything else.
**Parameters**
Encoded message
Wire format (defaults to ascii\_binary)
ISO 8583 version
Card network. Detected from the PAN when omitted.
The decoded message
# WireFormat
Source: https://iso8583sim.com/docs/api/wire/format
Wire formats: how an ISO 8583 message is laid out as bytes on the network.
`iso8583sim.wire.format`
## `Encoding`
Bases: `Enum`
How characters and digits are represented on the wire.
| Member | Value |
| - | - |
\| `ASCII` | `'ascii'` |
\| `EBCDIC` | `'ebcdic'` |
\| `BCD` | `'bcd'` |
\| `BINARY` | `'binary'` |
## `WireFormat`
```python theme={null}
WireFormat(
mti: Encoding = Encoding.ASCII,
bitmap: str = 'binary',
length_prefix: Encoding = Encoding.ASCII,
numeric: Encoding = Encoding.ASCII,
text: Encoding = Encoding.ASCII,
binary_fields: str = 'raw',
ebcdic_codec: str = 'cp037',
bcd_odd_padding: str = 'left',
)
```
Byte layout of a message. Each part of the message can be encoded independently.
**Attributes**
MTI encoding: ASCII, EBCDIC or BCD
"binary" for 8 raw bytes per bitmap, or "hex" for 16 hex characters in
the text encoding
Encoding of LLVAR / LLLVAR length indicators: ASCII, EBCDIC, BCD or BINARY
Encoding of numeric field content: ASCII, EBCDIC or BCD
Encoding of alphanumeric content: ASCII or EBCDIC
"raw" to send binary fields (PIN block, MAC, EMV) as bytes, or "hex"
to send them as hex characters in the text encoding
Python codec used for EBCDIC (cp037 is US/Canada, cp500 international)
Filler nibble position for odd-length variable BCD fields
### `ascii_hex()`
```python theme={null}
def ascii_hex() -> WireFormat
```
Everything as ASCII text with a hex bitmap: the byte form of this library's string messages.
### `ascii_binary()`
```python theme={null}
def ascii_binary() -> WireFormat
```
ASCII text and numbers with a binary bitmap and raw binary fields. A common host format.
### `bcd()`
```python theme={null}
def bcd() -> WireFormat
```
BCD MTI, numbers and lengths, binary bitmap, ASCII text. Typical of POS terminal links.
### `ebcdic()`
```python theme={null}
def ebcdic() -> WireFormat
```
EBCDIC text and numbers with a binary bitmap. Used by IBM mainframe hosts.
### `preset()`
```python theme={null}
def preset(name: str) -> WireFormat
```
Look up a preset by name: ascii-hex, ascii-binary, bcd or ebcdic.
# Message Flow
Source: https://iso8583sim.com/docs/architecture/message-flow
This document describes how ISO 8583 messages flow through iso8583sim.
## Parsing Flow
```
Raw Message String
│
▼
┌───────────────────┐
│ ISO8583Parser │
│ │
│ 1. _parse_mti() │──▶ Extract 4-character MTI
│ │
│ 2. _parse_bitmap()│──▶ Parse primary bitmap (8 bytes hex)
│ │ Check bit 1 for secondary bitmap
│ │
│ 3. _get_present_ │──▶ Convert bitmap to list of
│ fields() │ present field numbers
│ │
│ 4. _parse_fields()│──▶ For each present field:
│ │ - Get field definition
│ │ - Parse based on type (fixed/LLVAR/LLLVAR)
│ │ - Detect network from PAN
│ │
│ 5. _parse_emv() │──▶ If Field 55 present:
│ (optional) │ Parse TLV tags
│ │
└───────────────────┘
│
▼
ISO8583Message
```
### Parser Methods
| Method | Purpose |
| - | - |
| `parse()` | Main entry point, orchestrates parsing |
| `_parse_mti()` | Extract MTI (positions 0-3) |
| `_parse_bitmap()` | Parse 16-hex-char primary bitmap |
| `_get_present_fields()` | Convert bitmap bits to field numbers |
| `_parse_fields()` | Parse individual field values |
| `_parse_fixed_field()` | Parse fixed-length field |
| `_parse_variable_field()` | Parse LLVAR/LLLVAR field |
| `_detect_network()` | Identify card network from PAN |
## Building Flow
```
ISO8583Message
│
▼
┌───────────────────┐
│ ISO8583Builder │
│ │
│ 1. Format fields │──▶ For each field:
│ │ - Get field definition
│ │ - Apply padding/formatting
│ │
│ 2. Validate │──▶ ISO8583Validator.validate_message()
│ │ - Check required fields
│ │ - Validate field types
│ │ - Network-specific rules
│ │
│ 3. Build MTI │──▶ Append 4-character MTI
│ │
│ 4. Build bitmap │──▶ Create bitmap from present fields
│ │ - Primary: fields 1-64
│ │ - Secondary: fields 65-128
│ │
│ 5. Build fields │──▶ For each field in order:
│ │ - Add length prefix (LLVAR/LLLVAR)
│ │ - Append field value
│ │
└───────────────────┘
│
▼
Raw Message String
```
### Builder Methods
| Method | Purpose |
| - | - |
| `build()` | Main entry point, orchestrates building |
| `_format_field_value()` | Apply padding and formatting rules |
| `_build_bitmap()` | Generate bitmap from present fields |
| `_build_field()` | Build individual field with length prefix |
## Validation Flow
```
ISO8583Message
│
▼
┌────────────────────────┐
│ ISO8583Validator │
│ │
│ 1. validate_mti() │──▶ Check MTI format (4 digits)
│ │ Validate message class/function
│ │
│ 2. validate_bitmap() │──▶ Check bitmap format (16 hex)
│ │ Verify field presence matches
│ │
│ 3. validate_fields() │──▶ For each field:
│ │ - Check type (n/a/an/b)
│ │ - Validate length
│ │ - Apply field-specific rules
│ │
│ 4. Network validation │──▶ Check required fields present
│ (if network set) │ Apply network-specific rules
│ │
└────────────────────────┘
│
▼
List[str] (errors)
```
## EMV Data Flow
Field 55 contains EMV chip card data in TLV (Tag-Length-Value) format:
```
Field 55 Raw Data
│
▼
┌────────────────────────┐
│ parse_emv_data() │
│ │
│ While data remains: │
│ 1. Read tag (1-3 bytes)│
│ 2. Read length (1-3 bytes)│
│ 3. Read value (length bytes)│
│ 4. Store in dictionary │
│ │
└────────────────────────┘
│
▼
Dict[tag, value]
e.g., {"9F26": "ABCD...", "9F27": "80"}
```
## Object Pooling (High-Throughput)
For high-throughput scenarios, `MessagePool` reduces object allocation:
```
┌──────────────────┐
│ MessagePool │
│ │
│ acquire() │──▶ Get message from pool or create new
│ │
│ release() │──▶ Reset and return message to pool
│ │
│ Parser uses: │
│ pool.acquire() │──▶ Get blank message
│ ... parse ... │
│ return message │
│ │
│ User returns: │
│ pool.release() │──▶ Message reused next time
│ │
└──────────────────┘
```
## Cython Acceleration
When Cython extensions are compiled, critical functions are replaced:
| Python Function | Cython Function | Speedup |
| - | - | - |
| `_parse_mti()` | `parse_mti_fast()` | \~2x |
| `_parse_bitmap()` | `parse_bitmap_fast()` | \~2x |
| `_get_present_fields()` | `get_present_fields_fast()` | \~1.5x |
| `_validate_pan_luhn()` | `validate_pan_luhn_fast()` | \~3x |
Detection is automatic:
```python theme={null}
try:
from ._parser_fast import parse_bitmap_fast
_USE_CYTHON = True
except ImportError:
_USE_CYTHON = False
```
# Module Structure
Source: https://iso8583sim.com/docs/architecture/module-structure
This document describes the package organization and module responsibilities.
## Package Overview
```
iso8583sim/
├── __init__.py # Package root (minimal)
├── core/ # Core message handling
├── llm/ # LLM-powered features
├── cli/ # Command-line interface
├── mcp/ # MCP server for AI assistants
├── security/ # PIN blocks and MACs
├── web/ # REST API (FastAPI)
└── demo.py # Interactive demo helpers
```
## Core Module (`iso8583sim.core`)
The core module handles ISO 8583 message parsing, building, and validation.
### types.py
Central type definitions and field specifications.
**Classes:**
* `ISO8583Version` - Protocol version enum (1987, 1993, 2003)
* `FieldType` - Field data type enum (NUMERIC, ALPHA, LLVAR, etc.)
* `CardNetwork` - Card network enum (VISA, MASTERCARD, etc.)
* `MessageClass` - Message class enum (AUTHORIZATION, FINANCIAL, etc.)
* `MessageFunction` - Message function enum (REQUEST, RESPONSE, etc.)
* `FieldDefinition` - Field metadata dataclass
* `ISO8583Message` - Message data structure
* `ParseError` / `BuildError` - Exception types
**Data:**
* `ISO8583_FIELDS` - Standard ISO 8583 field definitions (128 fields)
* `NETWORK_SPECIFIC_FIELDS` - Network-specific field overrides
* `VERSION_SPECIFIC_FIELDS` - Version-specific field overrides
**Functions:**
* `get_field_definition()` - Get field definition with network/version overrides
* `detect_network()` - Identify card network from PAN
### parser.py
Message parsing from raw string to `ISO8583Message`.
**Classes:**
* `ISO8583Parser` - Main parser class
* `EMVTag` - EMV tag data structure
**Key Methods:**
* `parse(message, network=None)` - Parse raw message string
* `_parse_mti()` - Extract Message Type Indicator
* `_parse_bitmap()` - Parse primary/secondary bitmaps
* `_parse_fields()` - Parse individual fields
### builder.py
Message building from `ISO8583Message` to raw string.
**Classes:**
* `ISO8583Builder` - Main builder class
**Key Methods:**
* `build(message)` - Build raw message string
* `_format_field_value()` - Format field with padding
* `_build_bitmap()` - Generate bitmap from fields
* `_build_field()` - Build field with length prefix
### validator.py
Message validation with network-specific rules.
**Classes:**
* `ISO8583Validator` - Main validator class
**Key Methods:**
* `validate_message(message)` - Full message validation
* `validate_field(field_number, value, field_def)` - Single field validation
* `_validate_pan_luhn()` - PAN Luhn checksum validation
* `_validate_visa_specific()` - VISA-specific rules
* `_validate_mastercard_specific()` - Mastercard-specific rules
### emv.py
EMV/ICC chip card data handling.
**Functions:**
* `parse_emv_data(data)` - Parse Field 55 TLV data
* `build_emv_data(tags)` - Build Field 55 from tags
* `format_emv_tag()` - Format EMV tag for display
### codes.py
Reference tables and lookups for code values.
**Constants:**
* `RESPONSE_CODES` - Response codes (field 39) and meanings
* `PROCESSING_CODES` - Transaction types (first two digits of field 3)
* `NETWORK_MANAGEMENT_CODES` - Network management codes (field 70)
* `CURRENCY_CODES` - Common ISO 4217 codes with minor unit exponents
**Functions:**
* `detect_network_from_pan(pan)` - Detect the card network from a PAN prefix
### pool.py
Object pooling for high-throughput scenarios.
**Classes:**
* `MessagePool` - Reusable message object pool
**Key Methods:**
* `acquire()` - Get message from pool
* `release(message)` - Return message to pool
## LLM Module (`iso8583sim.llm`)
AI-powered message explanation and generation.
### base.py
Provider interface and exceptions.
**Classes:**
* `LLMProvider` - Abstract base class for providers
* `LLMResponse` - Response with metadata dataclass
* `LLMError` - Base LLM exception
* `ProviderConfigError` - Configuration error
* `ProviderNotAvailableError` - Missing provider/package
### explainer.py
Message explanation using LLMs.
**Classes:**
* `MessageExplainer` - Explain messages in plain English
**Key Methods:**
* `explain(message, verbose=False)` - Explain a message
* `explain_field(field_number, value)` - Explain a single field
* `explain_response_code(code)` - Explain response code
### generator.py
Message generation from natural language.
**Classes:**
* `MessageGenerator` - Generate messages from descriptions
**Key Methods:**
* `generate(description, validate=True)` - Generate message
* `suggest_fields(partial_message)` - Suggest missing fields
### providers/
LLM provider implementations.
**Files:**
* `__init__.py` - Provider factory and auto-detection
* `anthropic.py` - Anthropic (Claude) provider
* `openai.py` - OpenAI (GPT) provider
* `google.py` - Google (Gemini) provider
* `ollama.py` - Ollama (local) provider
**Key Functions:**
* `get_provider(name=None)` - Get provider instance
* `list_available_providers()` - List configured providers
* `list_installed_providers()` - List installed providers
## CLI Module (`iso8583sim.cli`)
Command-line interface using Click.
### commands.py
CLI command implementations.
**Commands:**
* `parse` - Parse a raw ISO 8583 message
* `build` - Build a message from fields
* `validate` - Validate a message
* `generate` - Generate sample messages
* `mcp` - Run the MCP server over stdio
## MCP Module (`iso8583sim.mcp`)
MCP server that exposes the core module to AI assistants. Requires the `mcp` extra.
**Functions:**
* `create_server()` - Build the server with its tools, resources and prompts
* `main()` - Run the server over stdio
See [MCP Server](../mcp-server) for the full tool list.
## Demo Module (`iso8583sim.demo`)
Interactive helpers for notebooks and exploration.
**Functions:**
* `generate_auth_request()` - Generate authorization request
* `generate_emv_auth()` - Generate EMV authorization
* `pretty_print()` - Format message for display
## Cython Extensions
Optional compiled extensions for performance.
**Files:**
* `core/_parser_fast.pyx` - Fast parsing functions
* `core/_bitmap.pyx` - Fast bitmap operations
* `core/_validator_fast.pyx` - Fast validation functions
**Detection:**
```python theme={null}
try:
from ._parser_fast import parse_mti_fast
_USE_CYTHON = True
except ImportError:
_USE_CYTHON = False
```
## Import Patterns
### Basic Usage
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.types import ISO8583Message
```
### With Validation
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import CardNetwork
```
### With LLM Features
```python theme={null}
from iso8583sim.llm import MessageExplainer, MessageGenerator
from iso8583sim.llm import get_provider, list_available_providers
```
### Demo/Interactive
```python theme={null}
from iso8583sim.demo import generate_auth_request, pretty_print
```
# Architecture Overview
Source: https://iso8583sim.com/docs/architecture/overview
iso8583sim is designed with a focus on performance, extensibility, and ease of use.
## Design Principles
### 1. Network-Agnostic Core
The core parsing and building logic is network-agnostic. Network-specific handling (VISA, Mastercard, etc.) is layered on top through:
* `CardNetwork` enum for network identification
* `NETWORK_SPECIFIC_FIELDS` for network-specific field definitions
* `NETWORK_REQUIRED_FIELDS` for network validation rules
### 2. Performance First
Performance optimizations include:
* **Optional Cython extensions**: 2x speedup for parsing operations
* **Object pooling**: Reusable message objects for high-throughput scenarios
* **Pre-compiled regex patterns**: Avoid recompilation overhead
* **Cached field lookups**: Network and version-specific field caching
### 3. Optional LLM Integration
LLM features are completely optional:
* Core functionality works without any LLM dependencies
* Provider-agnostic design supports Anthropic, OpenAI, Google, Ollama
* Auto-detection of available providers based on installed packages and API keys
## Package Structure
```
iso8583sim/
├── core/ # Core message handling
│ ├── types.py # Data types, enums, field definitions
│ ├── parser.py # Message parsing
│ ├── builder.py # Message building
│ ├── validator.py # Message validation
│ ├── emv.py # EMV/TLV data handling
│ ├── pool.py # Object pooling
│ └── _*.pyx # Cython extensions (optional)
├── llm/ # LLM-powered features
│ ├── base.py # Provider interface
│ ├── explainer.py # Message explanation
│ ├── generator.py # Message generation
│ └── providers/ # LLM provider implementations
│ ├── anthropic.py
│ ├── openai.py
│ ├── google.py
│ └── ollama.py
├── cli/ # Command-line interface
│ └── commands.py # Click-based CLI commands
├── mcp/ # MCP server for AI assistants
├── security/ # PIN blocks and MACs
├── web/ # REST API (FastAPI)
└── demo.py # Interactive demo helpers
```
## Key Components
### ISO8583Message
The central data structure representing a parsed or constructed message:
```python theme={null}
@dataclass(slots=True)
class ISO8583Message:
mti: str # Message Type Indicator
fields: dict[int, str] # Field number -> value
bitmap: str = "" # Primary bitmap (hex)
secondary_bitmap: str | None = None # Secondary bitmap
network: CardNetwork | None = None # Detected card network
version: ISO8583Version = ISO8583Version.V1987
raw: str = "" # Original raw message
emv_data: dict[str, str] | None = None # Parsed Field 55 EMV data
```
### ISO8583Parser
Stateful parser that converts raw message strings into `ISO8583Message` objects:
* Auto-detects card network from PAN prefix
* Supports ISO 8583 versions 1987, 1993, 2003
* Handles primary and secondary bitmaps
* Parses LLVAR/LLLVAR variable-length fields
* Optional Cython acceleration
### ISO8583Builder
Constructs raw ISO 8583 message strings from `ISO8583Message` objects:
* Validates messages before building
* Handles field formatting (padding, length prefixes)
* Builds bitmap from present fields
* Network-aware field formatting
### ISO8583Validator
Validates message structure and field content:
* MTI validation
* Field type validation (numeric, alpha, alphanumeric, binary)
* Network-specific required field validation
* PAN Luhn check validation
### EMV Handling
Field 55 (ICC Data) contains EMV chip card data in TLV format:
* `parse_emv_data()`: Parse TLV bytes into tag dictionary
* `build_emv_data()`: Build TLV bytes from tag dictionary
* Support for multi-byte tags (9F26, 9F27, etc.)
## Data Flow
See [Message Flow](./message-flow) for detailed data flow diagrams.
## Extension Points
### Adding New Card Networks
1. Add enum value to `CardNetwork`
2. Add field definitions to `NETWORK_SPECIFIC_FIELDS`
3. Add required fields to `NETWORK_REQUIRED_FIELDS`
4. Add validation method to `ISO8583Validator`
### Adding LLM Providers
1. Implement `LLMProvider` interface
2. Add provider to `_PROVIDERS` registry in `providers/__init__.py`
3. Implement `is_available()` function
### Custom Field Definitions
```python theme={null}
from iso8583sim.core.types import FieldDefinition, FieldType
custom_field = FieldDefinition(
field_type=FieldType.ALPHANUMERIC,
max_length=20,
description="Custom field",
field_number=120,
)
```
# CLI Reference
Source: https://iso8583sim.com/docs/cli/index
iso8583sim provides a command-line interface for parsing, building, and validating ISO 8583 messages.
## Installation
The CLI is included with iso8583sim:
```bash theme={null}
pip install iso8583sim
```
## Basic Usage
```bash theme={null}
# Get help
iso8583sim --help
# Parse a message
iso8583sim parse "0100702406C120E09000..."
# Build a message
iso8583sim build --mti 0100 --fields fields.json
# Validate a message
iso8583sim validate "0100702406C120E09000..."
# Generate sample messages
iso8583sim generate --type auth --pan 4111111111111111
```
## Commands
### parse
Parse a raw ISO 8583 message and display its contents.
```bash theme={null}
iso8583sim parse MESSAGE [OPTIONS]
```
**Arguments:**
| Argument | Description |
| - | - |
| `MESSAGE` | Raw ISO 8583 message as hex string |
**Options:**
| Option | Description |
| - | - |
| `--version` | ISO 8583 version (1987, 1993, 2003) |
| `--network` | Card network (visa, mastercard, amex, etc.) |
| `--format` | Output format (table, json, raw) |
| `--verbose` | Show detailed field information |
**Examples:**
```bash theme={null}
# Basic parsing
iso8583sim parse "0100702406C120E09000..."
# With version specification
iso8583sim parse "0100..." --version 1993
# JSON output
iso8583sim parse "0100..." --format json
# Verbose output with field descriptions
iso8583sim parse "0100..." --verbose
```
### build
Build an ISO 8583 message from field values.
```bash theme={null}
iso8583sim build [OPTIONS]
```
**Options:**
| Option | Description |
| - | - |
| `--mti` | Message Type Indicator (required) |
| `--fields` | JSON file with field values |
| `--field` | Individual field (can be repeated): `--field 2=4111111111111111` |
| `--version` | ISO 8583 version |
| `--output` | Output file (default: stdout) |
**Examples:**
```bash theme={null}
# Build from JSON file
iso8583sim build --mti 0100 --fields fields.json
# Build with inline fields
iso8583sim build --mti 0100 \
--field 2=4111111111111111 \
--field 3=000000 \
--field 4=000000010000
# Save to file
iso8583sim build --mti 0100 --fields fields.json --output message.hex
```
**fields.json format:**
```json theme={null}
{
"2": "4111111111111111",
"3": "000000",
"4": "000000010000",
"11": "123456",
"41": "TERM0001",
"42": "MERCHANT123456 "
}
```
### validate
Validate an ISO 8583 message for structure and content.
```bash theme={null}
iso8583sim validate MESSAGE [OPTIONS]
```
**Options:**
| Option | Description |
| - | - |
| `--network`, `-n` | Validate against one network's requirements |
| `--against`, `-a` | Check against several networks: comma separated names, or `all` |
| `--version`, `-v` | ISO 8583 version (1987, 1993, 2003) |
**Examples:**
```bash theme={null}
# Basic validation
iso8583sim validate "0100..."
# With network validation
iso8583sim validate "0100..." --network visa
# Which networks would accept this message?
iso8583sim validate "0100..." --against all
```
**Output (`--against all`):**
```
Network Compliance
┏━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Network ┃ Result ┃ Issues ┃
┡━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ VISA │ PASS │ - │
│ MASTERCARD │ PASS │ - │
│ UNIONPAY │ FAIL │ Required field 49 missing for UNIONPAY │
└────────────┴────────┴────────────────────────────────────────┘
```
The command exits with status 1 when any check fails.
### convert
Convert a message to another ISO 8583 version.
```bash theme={null}
iso8583sim convert MESSAGE --to VERSION [OPTIONS]
```
**Options:**
| Option | Description |
| - | - |
| `--to`, `-t` | Target version (1987, 1993, 2003) |
| `--from`, `-f` | Version of the input message (default 1987) |
| `--network`, `-n` | Card network. Network rules are only enforced on the output when this is given |
| `--output`, `-o` | Output file |
**Example:**
```bash theme={null}
iso8583sim convert "0400..." --to 2003
```
The output shows the converted message, notes about fields whose meaning differs between versions, and any fields that had to be dropped. See [Version Conversion](../core/convert) for the rules.
### explain
Explain a message in plain English.
```bash theme={null}
iso8583sim explain MESSAGE [OPTIONS]
```
By default the explanation comes from an LLM (see [LLM Features](../llm) for provider setup). With `--no-llm` you get a rule-based summary instead, which works offline and needs no API key.
**Options:**
| Option | Description |
| - | - |
| `--no-llm` | Rule-based summary, no LLM or API key needed |
| `--provider`, `-p` | LLM provider: `anthropic`, `openai`, `google` or `ollama`. Auto-detected if omitted |
| `--model`, `-m` | Model name for the provider |
| `--verbose` | Ask the LLM for more technical detail |
| `--version`, `-v` | ISO 8583 version (1987, 1993, 2003) |
| `--network`, `-n` | Card network (detected from the PAN if omitted) |
**Examples:**
```bash theme={null}
# Explain with the first configured LLM provider
iso8583sim explain "0100..."
# Use a specific provider and model
iso8583sim explain "0100..." --provider ollama --model qwen3
# Offline, rule-based summary
iso8583sim explain "0110..." --no-llm
```
**Output (`--no-llm`):**
```
MTI 0110: authorization response from acquirer. Card network: MASTERCARD. Card:
555555******4444. Transaction type: Purchase. Amount: 25.00 EUR. Response code 51:
Insufficient funds. Terminal: TERM0001. Merchant: MERCHANT123456.
```
followed by a table of every field.
### generate
Generate sample ISO 8583 messages from a template, or from a plain English description with `--llm`.
```bash theme={null}
iso8583sim generate [OPTIONS]
```
**Options:**
| Option | Description |
| - | - |
| `--type`, `-t` | Message type (auth, financial, reversal, network). Required unless `--llm` is given |
| `--pan`, `-p` | Primary Account Number |
| `--amount`, `-a` | Amount in minor units (`1000` is 10.00), or a decimal such as `10.00`. Uses the currency's decimal places, so JPY `1500` is 1500 yen |
| `--currency`, `-c` | Currency code (ISO 4217, default 840) |
| `--network`, `-n` | Card network |
| `--llm`, `-l` | Describe the message in plain English and let an LLM build it |
| `--provider` | LLM provider for `--llm` |
| `--model` | Model name for `--llm` |
| `--output`, `-o` | Output file |
**Examples:**
```bash theme={null}
# Generate authorization request
iso8583sim generate --type auth --pan 4111111111111111 --amount 10000
# Save to file
iso8583sim generate --type auth --output message.txt
# Generate from a description
iso8583sim generate --llm "$50 refund to a Mastercard at ACME Store"
```
With `--llm`, the generated message is validated before it's shown, and common problems are fixed automatically.
### serve
Run a mock issuer host that answers requests by rules. See [Networking](../net) for the rules file format.
```bash theme={null}
iso8583sim serve [--host 127.0.0.1] [--port 8583] [--rules rules.yaml] [--format bcd] [--header 2b] [--tpdu 6000010000]
```
Stops on Ctrl+C or SIGTERM and prints a summary of the requests it received and how it answered them.
### send
Send one message to a host and show the response.
```bash theme={null}
iso8583sim send MESSAGE [--host 127.0.0.1] [--port 8583] [--format ascii-binary] [--header 2b] [--tpdu HEX] [--timeout 10]
```
Exits with status 1 on a timeout or connection error.
### load
Send many generated requests to a host and report throughput, latency percentiles and response codes.
```bash theme={null}
iso8583sim load [--host 127.0.0.1] [--port 8583] [--count 1000] [--concurrency 10] [--connections 1] [--network VISA]
```
Takes the same `--format`, `--header`, `--tpdu` and `--timeout` options as `send`. Exits with status 1 if any request timed out or failed.
### web
Run the REST API server. Requires `pip install iso8583sim[web]`.
```bash theme={null}
iso8583sim web [--host 127.0.0.1] [--port 8000] [--reload]
```
Interactive API docs are served at `/docs`. See [REST API](../web).
### mcp
Run the MCP server over stdio so AI assistants can use iso8583sim. Requires `pip install iso8583sim[mcp]`.
```bash theme={null}
iso8583sim mcp
```
Your MCP client normally starts this command for you. See [MCP Server](../mcp-server) for setup.
## Output Formats
### Table (default)
```
┌─────────┬───────────────────────────┬────────────────────────┐
│ Field │ Value │ Description │
├─────────┼───────────────────────────┼────────────────────────┤
│ MTI │ 0100 │ Authorization Request │
│ 2 │ 4111111111111111 │ Primary Account Number │
│ 3 │ 000000 │ Processing Code │
│ 4 │ 000000010000 │ Amount │
└─────────┴───────────────────────────┴────────────────────────┘
```
### JSON
```bash theme={null}
iso8583sim parse "0100..." --format json
```
```json theme={null}
{
"mti": "0100",
"bitmap": "7024058020C09000",
"fields": {
"2": "4111111111111111",
"3": "000000",
"4": "000000010000"
},
"network": "VISA"
}
```
### Raw
```bash theme={null}
iso8583sim parse "0100..." --format raw
```
```
0100
7024058020C09000
2: 4111111111111111
3: 000000
4: 000000010000
```
## Exit Codes
| Code | Meaning |
| - | - |
| 0 | Success |
| 1 | Parse error |
| 2 | Validation error |
| 3 | Build error |
| 4 | File not found |
## Environment Variables
| Variable | Description |
| - | - |
| `ISO8583SIM_VERSION` | Default ISO 8583 version |
| `ISO8583SIM_NETWORK` | Default card network |
| `ANTHROPIC_API_KEY` | For LLM features |
| `OPENAI_API_KEY` | For LLM features |
## Piping and Scripting
```bash theme={null}
# Pipe messages
cat messages.txt | while read line; do
iso8583sim parse "$line" --format json
done
# Parse from file
iso8583sim parse "$(cat message.hex)"
# Build and parse roundtrip
iso8583sim build --mti 0100 --fields fields.json | iso8583sim parse -
```
## Troubleshooting
### Common Issues
**"Command not found":**
```bash theme={null}
# Ensure iso8583sim is in PATH
pip show iso8583sim # Check installation
python -m iso8583sim.cli --help # Alternative invocation
```
**"Invalid hex string":**
```bash theme={null}
# Ensure message contains only hex characters (0-9, A-F)
# Remove any spaces or newlines
```
**"Parse error: Invalid bitmap":**
```bash theme={null}
# Check that bitmap is 16 hex characters
# Primary bitmap must be exactly 8 bytes (16 hex chars)
```
# Builder
Source: https://iso8583sim.com/docs/core/builder
The ISO8583Builder class constructs raw ISO 8583 message strings from ISO8583Message objects.
## Quick Start
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.types import ISO8583Message
builder = ISO8583Builder()
# Define a message
message = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111", # PAN
3: "000000", # Processing Code
4: "000000001000", # Amount ($10.00)
11: "123456", # STAN
41: "TERM0001", # Terminal ID
42: "MERCHANT123456 ", # Merchant ID
}
)
# Build the raw message
raw_message = builder.build(message)
print(raw_message)
```
## Features
### Automatic Validation
The builder validates messages before building:
```python theme={null}
from iso8583sim.core.types import BuildError
try:
raw = builder.build(message)
except BuildError as e:
print(f"Build error: {e}")
```
### Field Formatting
Fields are automatically formatted based on their definitions:
* **Numeric fields**: Left-padded with zeros
* **Alphanumeric fields**: Right-padded with spaces (where required)
* **Terminal ID (41)**: Right-padded to 8 characters
* **Merchant ID (42)**: Right-padded to 15 characters
```python theme={null}
# Input
message.fields[41] = "TERM1"
message.fields[42] = "MERCHANT"
# Output (after build)
# Field 41: "TERM1 " (8 chars)
# Field 42: "MERCHANT " (15 chars)
```
### Variable-Length Fields
LLVAR and LLLVAR fields get automatic length prefixes:
```python theme={null}
# Field 2 (PAN) is LLVAR
message.fields[2] = "4111111111111111"
# In raw message: "164111111111111111"
# ^^ length prefix (16 chars)
```
### Bitmap Generation
The bitmap is automatically generated from present fields:
```python theme={null}
message = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111", # Bit 2
3: "000000", # Bit 3
4: "000000001000", # Bit 4
}
)
# Bitmap: bits 2, 3, 4 set
# Binary: 0111 0000 0000 0000 ...
# Hex: 7000000000000000
```
### Network-Aware Building
Specify network for network-specific field formatting:
```python theme={null}
from iso8583sim.core.types import CardNetwork
message = ISO8583Message(
mti="0100",
network=CardNetwork.VISA,
fields={...}
)
raw = builder.build(message)
```
## Building Process
1. **Field Processing**: Format each field value based on definition
2. **Validation**: Validate message structure and field content
3. **MTI**: Append 4-character MTI
4. **Bitmap**: Generate and append bitmap from present fields
5. **Fields**: Append each field with length prefix (if variable)
## Common Field Examples
### Authorization Request (0100)
```python theme={null}
auth_request = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111", # PAN
3: "000000", # Processing Code (purchase)
4: "000000001000", # Amount
7: "1225120000", # Date/Time
11: "123456", # STAN
14: "2512", # Expiry (YYMM)
22: "051", # POS Entry Mode
24: "100", # NII
25: "00", # POS Condition Code
41: "TERM0001", # Terminal ID
42: "MERCHANT123456 ", # Merchant ID
}
)
```
### Financial Request (0200)
```python theme={null}
financial_request = ISO8583Message(
mti="0200",
fields={
0: "0200",
2: "4111111111111111",
3: "000000",
4: "000000001000",
11: "123457",
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
```
### Reversal (0400)
```python theme={null}
reversal = ISO8583Message(
mti="0400",
fields={
0: "0400",
2: "4111111111111111",
3: "000000",
4: "000000001000",
11: "123456", # Original STAN
37: "123456789012", # Original retrieval reference
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
```
## Error Handling
```python theme={null}
from iso8583sim.core.types import BuildError
try:
raw = builder.build(message)
except BuildError as e:
print(f"Build failed: {e}")
```
Common errors:
* Missing required fields
* Invalid field format
* Field value exceeds maximum length
## API Reference
See [Core API Reference](../api/core#iso8583simcorebuilder) for complete API documentation.
# Version Conversion
Source: https://iso8583sim.com/docs/core/convert
convert_message converts a message between the 1987, 1993 and 2003 versions of ISO 8583.
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.convert import convert_message
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.types import ISO8583Version
parsed = ISO8583Parser().parse(raw_1987_message)
result = convert_message(parsed, ISO8583Version.V2003)
raw_2003 = ISO8583Builder(version=ISO8583Version.V2003).build(result.message)
result.notes # Things worth checking, e.g. fields whose meaning changed
result.dropped # {field_number: reason} for fields that could not be carried over
result.lossless # True when nothing was dropped
```
## What changes
| Item | Rule |
| - | - |
| MTI | The first digit is the version: `0` (1987), `1` (1993), `2` (2003). `0200` becomes `1200` or `2200`. |
| Original data elements | Moved from field 90 (1987, fixed 42) to field 56 (2003, LLLVAR) and back, unless the destination field is already set. |
| Other fields | Checked against the target version's definition and kept when they fit. |
## What gets dropped
Fields are dropped, never truncated, and each drop has a reason in `result.dropped`:
* **Field 52 (PIN data)** when the PIN block size differs (8, 16 or 32 bytes). A PIN block can't be padded to a new size. Re-encrypt it for the target version, for example as a format 4 block with `iso8583sim.security.encrypt_pin_block`.
* **Field 53 (security control information)** when its definition differs.
* **Fields 64 and 128 (MAC)**. The MAC covers the whole message, so it must be recomputed after conversion with `ISO8583Builder.build_with_mac`.
* **Values that don't fit**, such as a 60 character field 43 converted to 1987, where field 43 is a fixed 40 characters.
* **Fields not defined** in the target version.
## Notes
Fields 57 to 59 are reserved in 1987 but have defined meanings in 2003 (for example, field 57 is the Authorization Life Cycle Code). Their values are carried over, with a note to check that the value still means the same thing.
A message converted to another version and back reproduces the original exactly, as long as nothing was dropped.
## CLI
```bash theme={null}
iso8583sim convert "0400..." --to 2003
```
See the [CLI reference](../cli#convert).
# EMV Data
Source: https://iso8583sim.com/docs/core/emv
The EMV module handles Field 55 (ICC Data) which contains chip card data in TLV (Tag-Length-Value) format.
## Quick Start
```python theme={null}
from iso8583sim.core.emv import parse_emv_data, build_emv_data
# Parse EMV data
emv_hex = "9F2608..." # Raw TLV data
tags = parse_emv_data(emv_hex)
print(tags.get("9F26")) # Application Cryptogram
print(tags.get("9F27")) # Cryptogram Information Data
# Build EMV data
tags = {
"9F26": "1234567890ABCDEF",
"9F27": "80",
"9F10": "0110A00003220000",
}
emv_hex = build_emv_data(tags)
```
## TLV Format
EMV data uses Tag-Length-Value encoding:
```
┌───────────────┬───────────────┬────────────────────────┐
│ Tag │ Length │ Value │
│ (1-3 bytes) │ (1-3 bytes) │ (Length bytes) │
└───────────────┴───────────────┴────────────────────────┘
```
### Tag Format
* **1-byte tags**: First byte doesn't have bits 1-5 set to 11111
* **2-byte tags**: First byte ends with 11111 (1F, 5F, 9F, DF)
* **3-byte tags**: Second byte has high bit set
Examples:
* `82` - Single byte (Application Interchange Profile)
* `9F26` - Two bytes (Application Cryptogram)
* `DF8101` - Three bytes (if encountered)
### Length Format
* **1-byte length**: 0x00-0x7F (0-127 bytes)
* **2-byte length**: 0x81 + 1 byte (128-255 bytes)
* **3-byte length**: 0x82 + 2 bytes (256-65535 bytes)
## Common EMV Tags
| Tag | Name | Description |
| - | - | - |
| 9F26 | Application Cryptogram | Transaction cryptogram from chip |
| 9F27 | Cryptogram Information Data | Type of cryptogram |
| 9F10 | Issuer Application Data | Issuer-specific data |
| 9F37 | Unpredictable Number | Random number for cryptogram |
| 9F36 | Application Transaction Counter | Incremental counter |
| 82 | Application Interchange Profile | Supported functions |
| 84 | Dedicated File Name | Application identifier |
| 9F33 | Terminal Capabilities | Terminal features |
| 9F34 | CVM Results | Cardholder verification results |
| 9F35 | Terminal Type | Terminal category |
| 9F1A | Terminal Country Code | Country where terminal is |
| 5F2A | Transaction Currency Code | Currency of transaction |
| 9A | Transaction Date | YYMMDD |
| 9C | Transaction Type | 00=purchase, 01=cash, etc. |
| 9F02 | Amount Authorized | Transaction amount |
## Cryptogram Types (Tag 9F27)
| Value | Type | Description |
| - | - | - |
| 00 | AAC | Application Authentication Cryptogram (decline) |
| 40 | TC | Transaction Certificate (offline approve) |
| 80 | ARQC | Authorization Request Cryptogram (online) |
## Parsing Examples
### Full EMV Data String
```python theme={null}
emv_data = (
"9F2608ABCDEF0123456789" # Cryptogram
"9F2701" # CID length
"80" # CID value (ARQC)
"9F100706010A03A40000" # Issuer Application Data
)
tags = parse_emv_data(emv_data)
print(tags)
# {
# "9F26": "ABCDEF0123456789",
# "9F27": "80",
# "9F10": "06010A03A40000"
# }
```
### From Parsed Message
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
parser = ISO8583Parser()
message = parser.parse(raw_message)
if message.emv_data:
cryptogram = message.emv_data.get("9F26")
cid = message.emv_data.get("9F27")
if cid == "80":
print("Online authorization requested (ARQC)")
elif cid == "40":
print("Offline approved (TC)")
elif cid == "00":
print("Offline declined (AAC)")
```
## Building Examples
### Build Field 55
```python theme={null}
from iso8583sim.core.emv import build_emv_data
tags = {
"9F26": "1234567890ABCDEF",
"9F27": "80",
"9F10": "0110A00003220000",
"9F37": "12345678",
"9F36": "0001",
}
field_55 = build_emv_data(tags)
print(field_55) # "9F26081234567890ABCDEF9F27018..."
```
### Add to Message
```python theme={null}
from iso8583sim.core.types import ISO8583Message
from iso8583sim.core.emv import build_emv_data
emv_tags = {
"9F26": "1234567890ABCDEF",
"9F27": "80",
}
message = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111",
3: "000000",
4: "000000025000",
55: build_emv_data(emv_tags),
}
)
```
## Integration with Demo Module
```python theme={null}
from iso8583sim.demo import generate_emv_auth
# Generate EMV authorization with demo data
message = generate_emv_auth(
pan="4111111111111111",
amount=25000,
cryptogram="1234567890ABCDEF",
)
print(message.fields.get(55)) # Contains built EMV TLV data
```
## API Reference
See [Core API Reference](../api/core#iso8583simcoreemv) for complete API documentation.
# Core Module
Source: https://iso8583sim.com/docs/core/index
The core module provides all ISO 8583 message handling functionality.
## Components
| Component | Description |
| - | - |
| [Types & Fields](./types) | Data types, enums, and field definitions |
| [Parser](./parser) | Parse raw messages into structured objects |
| [Builder](./builder) | Build raw messages from objects |
| [Validator](./validator) | Validate message structure and content |
| [EMV Data](./emv) | Handle Field 55 chip card data |
| [Object Pool](./pool) | High-throughput object pooling |
## Quick Start
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import ISO8583Message, CardNetwork
# Parse
parser = ISO8583Parser()
message = parser.parse("0100702406C120E09000...")
# Build
builder = ISO8583Builder()
raw = builder.build(message)
# Validate
validator = ISO8583Validator()
errors = validator.validate_message(message)
```
## Features
### Network-Agnostic Design
The core module is network-agnostic by default. Network-specific behavior is added through:
* `CardNetwork` parameter for network-specific field definitions
* Auto-detection from PAN prefix when parsing
```python theme={null}
# Auto-detect network from PAN
message = parser.parse(raw_message)
print(message.network) # CardNetwork.VISA
# Or specify explicitly
message = parser.parse(raw_message, network=CardNetwork.MASTERCARD)
```
### Version Support
Supports ISO 8583 versions 1987, 1993, and 2003:
```python theme={null}
from iso8583sim.core.types import ISO8583Version
parser = ISO8583Parser(version=ISO8583Version.V1993)
builder = ISO8583Builder(version=ISO8583Version.V1993)
```
### Performance Optimization
Compiled Cython extensions provide \~2x speedup. They are included in the PyPI wheels for Linux, macOS and Windows, so `pip install iso8583sim` is enough.
Detection is automatic - the parser/validator use Cython functions when available.
### Object Pooling
For high-throughput scenarios, use `MessagePool`:
```python theme={null}
from iso8583sim.core.pool import MessagePool
from iso8583sim.core.parser import ISO8583Parser
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
# Messages are recycled automatically
for raw in raw_messages:
message = parser.parse(raw)
# ... process message ...
pool.release(message) # Return to pool
```
## Architecture
See [Module Structure](../architecture/module-structure) for detailed architecture documentation.
# Parser
Source: https://iso8583sim.com/docs/core/parser
The ISO8583Parser class converts raw ISO 8583 message strings into structured ISO8583Message objects.
## Quick Start
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
parser = ISO8583Parser()
# Parse a raw message
message = parser.parse("0100702406C120E09000164111111111111111...")
print(f"MTI: {message.mti}")
print(f"PAN: {message.fields.get(2)}")
print(f"Amount: {message.fields.get(4)}")
print(f"Network: {message.network}")
```
## Features
### Auto Network Detection
The parser automatically detects the card network from the PAN (Field 2):
```python theme={null}
message = parser.parse(raw_message)
print(message.network) # CardNetwork.VISA, CardNetwork.MASTERCARD, etc.
```
Network detection is based on PAN prefixes:
* **VISA**: 4xxx
* **Mastercard**: 51-55, 2221-2720
* **AMEX**: 34, 37
* **Discover**: 6011, 644-649, 65
* **JCB**: 3528-3589
* **UnionPay**: 62
### Manual Network Specification
Override auto-detection by specifying the network:
```python theme={null}
from iso8583sim.core.types import CardNetwork
message = parser.parse(raw_message, network=CardNetwork.MASTERCARD)
```
### Version Support
```python theme={null}
from iso8583sim.core.types import ISO8583Version
# Use ISO 8583:1993
parser = ISO8583Parser(version=ISO8583Version.V1993)
```
### EMV Data Parsing
Field 55 (ICC Data) is automatically parsed into a dictionary:
```python theme={null}
message = parser.parse(emv_message)
if message.emv_data:
print(f"Cryptogram: {message.emv_data.get('9F26')}")
print(f"CID: {message.emv_data.get('9F27')}")
```
### Object Pooling
For high-throughput scenarios:
```python theme={null}
from iso8583sim.core.pool import MessagePool
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
# Messages are recycled
message = parser.parse(raw)
# ... use message ...
pool.release(message)
```
## Parsing Process
1. **MTI Extraction**: Read first 4 characters as Message Type Indicator
2. **Bitmap Parsing**: Parse 16-character hex bitmap (64 bits)
3. **Secondary Bitmap**: If bit 1 is set, parse additional 16 characters
4. **Field Parsing**: For each set bit, parse the corresponding field
5. **Network Detection**: Identify card network from PAN if present
## Error Handling
```python theme={null}
from iso8583sim.core.types import ParseError
try:
message = parser.parse(raw_message)
except ParseError as e:
print(f"Parse error: {e}")
```
Common errors:
* Invalid MTI format
* Malformed bitmap
* Field length exceeds maximum
* Invalid hex characters
## API Reference
See [Core API Reference](../api/core#iso8583simcoreparser) for complete API documentation.
# Object Pool
Source: https://iso8583sim.com/docs/core/pool
The MessagePool class provides object pooling for high-throughput scenarios, reducing memory allocation overhead.
## Quick Start
```python theme={null}
from iso8583sim.core.pool import MessagePool
from iso8583sim.core.parser import ISO8583Parser
# Create a pool with 1000 pre-allocated messages
pool = MessagePool(size=1000)
# Create parser with pool
parser = ISO8583Parser(pool=pool)
# Parse messages (objects are recycled)
for raw_message in raw_messages:
message = parser.parse(raw_message)
# Process the message...
process(message)
# Return message to pool for reuse
pool.release(message)
```
## Why Object Pooling?
In high-throughput scenarios (10k+ TPS), object allocation becomes a bottleneck:
1. **Memory allocation**: Creating new objects is expensive
2. **Garbage collection**: Many short-lived objects trigger GC
3. **Cache locality**: Reused objects stay in CPU cache
Object pooling addresses these by reusing message objects.
## Performance Impact
| Scenario | Without Pool | With Pool |
| - | - | - |
| 10k messages | \~95k TPS | \~105k TPS |
| 100k messages | \~90k TPS | \~102k TPS |
| 1M messages | \~85k TPS | \~100k TPS |
*Benefits increase with message volume due to reduced GC pressure.*
## Usage Patterns
### Basic Usage
```python theme={null}
from iso8583sim.core.pool import MessagePool
from iso8583sim.core.parser import ISO8583Parser
pool = MessagePool(size=100)
parser = ISO8583Parser(pool=pool)
message = parser.parse(raw)
# ... use message ...
pool.release(message)
```
### Context Manager
```python theme={null}
with pool.acquire() as message:
# Configure message
message.mti = "0100"
message.fields[2] = "4111111111111111"
# ... build/process ...
# Automatically released when exiting context
```
### Batch Processing
```python theme={null}
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
results = []
for raw in batch:
msg = parser.parse(raw)
results.append(process(msg))
pool.release(msg)
```
### Thread Safety
The pool is thread-safe for concurrent access:
```python theme={null}
import concurrent.futures
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
def process_message(raw):
msg = parser.parse(raw)
result = process(msg)
pool.release(msg)
return result
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(process_message, raw_messages))
```
## Pool Sizing
Choose pool size based on:
1. **Concurrency level**: Number of messages processed simultaneously
2. **Message retention**: How long messages are held before release
3. **Memory constraints**: Each message \~1KB
**Rule of thumb**: `pool_size = concurrent_workers * 2`
```python theme={null}
# Single-threaded processing
pool = MessagePool(size=10)
# Multi-threaded (4 workers)
pool = MessagePool(size=100)
# High-throughput server
pool = MessagePool(size=1000)
```
## Pool Exhaustion
When pool is exhausted, new objects are created (no blocking):
```python theme={null}
pool = MessagePool(size=10)
# Acquire 15 messages
messages = []
for _ in range(15):
msg = pool.acquire()
messages.append(msg)
# First 10 from pool, next 5 created new
# Pool automatically expands
```
## Monitoring
```python theme={null}
pool = MessagePool(size=100)
print(f"Pool size: {pool.size}")
print(f"Available: {pool.available}")
print(f"In use: {pool.in_use}")
```
## Best Practices
### Do
```python theme={null}
# Always release messages when done
message = parser.parse(raw)
try:
process(message)
finally:
pool.release(message)
# Use context manager for automatic release
with pool.acquire() as message:
process(message)
```
### Don't
```python theme={null}
# Don't hold references after release
message = parser.parse(raw)
pool.release(message)
print(message.mti) # Bad! Message may be reused
# Don't release twice
pool.release(message)
pool.release(message) # Error or undefined behavior
```
## When to Use
**Use pooling when:**
* Processing > 10k messages per second
* Memory pressure is a concern
* GC pauses are noticeable
**Skip pooling when:**
* Processing \< 1k messages per second
* Messages need long-term storage
* Simplicity is preferred
## API Reference
See [Core API Reference](../api/core#iso8583simcorepool) for complete API documentation.
# PIN Blocks and MACs
Source: https://iso8583sim.com/docs/core/security
The iso8583sim.security module builds and checks the two security fields most test harnesses need: PIN blocks for field 52 and message authentication codes (MACs) for fields 64 and 128.
**Test and simulation use only.** Real payment systems keep clear PINs and keys inside a hardware security module (HSM). Use this module to generate test data and to check what your system under test produces, never to handle real cardholder PINs or production keys.
## Installation
```bash theme={null}
pip install iso8583sim[security]
```
This installs the `cryptography` package.
## PIN Blocks (ISO 9564-1)
| Format | Cipher | Size | Uses PAN | Fill |
| - | - | - | - | - |
| 0 | TDES | 8 bytes | Yes | `F` |
| 1 | TDES | 8 bytes | No | Random |
| 3 | TDES | 8 bytes | Yes | Random `A` to `F` |
| 4 | AES | 16 bytes | Yes | Random |
Formats 0, 1 and 3 fit field 52 in every version (8 bytes). Format 4 is 16 bytes, which fits field 52 in the 1993 and 2003 versions.
```python theme={null}
from iso8583sim.security import decrypt_pin_block, encrypt_pin_block
key = "0123456789ABCDEFFEDCBA9876543210" # Double length TDES key (test key)
block = encrypt_pin_block("1234", "4111111111111111", key, fmt=0)
print(block) # 2A3D408A1977DDE9
pin = decrypt_pin_block(block, "4111111111111111", key, fmt=0)
print(pin) # 1234
```
Format 4 needs an AES key (128, 192 or 256 bits):
```python theme={null}
aes_key = "00112233445566778899AABBCCDDEEFF"
block = encrypt_pin_block("1234", "4111111111111111", aes_key, fmt=4) # 32 hex characters
```
For clear (unencrypted) blocks in formats 0, 1 and 3, use `encode_pin_block` and `decode_pin_block`:
```python theme={null}
from iso8583sim.security import encode_pin_block
encode_pin_block("1234", "4111111111111111", fmt=0).hex().upper() # 041225EEEEEEEEEE
```
Decrypting with the wrong key raises `SecurityError` in every format, because the result fails the block's structure checks. A wrong PAN is always caught in format 4, but only sometimes in formats 0 and 3: there the PAN is applied with a single XOR, so a different PAN can decode to a well-formed block with a different PIN. That is a property of those formats.
## MACs (ISO 9797-1)
| Algorithm | Name | Key |
| - | - | - |
| 1 | CBC-MAC | 8, 16 or 24 bytes |
| 3 | Retail MAC (ANSI X9.19), default | 16 bytes |
Padding method 1 (zeros, the default) and method 2 (`0x80` then zeros) are supported.
### Signing and verifying messages
`build_with_mac` builds a message and puts its MAC in the last field: field 64, or field 128 when the message has a secondary bitmap.
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.types import ISO8583Message
from iso8583sim.core.validator import ISO8583Validator
mac_key = "0123456789ABCDEFFEDCBA9876543210"
message = ISO8583Message(
mti="0200",
fields={2: "4111111111111111", 3: "000000", 4: "000000001000", 11: "123456"},
)
raw = ISO8583Builder().build_with_mac(message, mac_key)
ISO8583Validator().verify_mac(raw, mac_key) # True
```
The MAC covers every character of the message before the MAC field, including the MTI and bitmap. Any change to the message makes verification fail.
### Raw data
```python theme={null}
from iso8583sim.security import generate_mac, verify_mac
mac = generate_mac(b"data to authenticate", mac_key, algorithm=3, padding=1)
verify_mac(b"data to authenticate", mac, mac_key) # True
```
Pass `length=4` to `generate_mac` for a 4-byte MAC. `verify_mac` takes the length from the MAC you give it.
# Types & Fields
Source: https://iso8583sim.com/docs/core/types
The types module defines all data types, enums, and field definitions used throughout iso8583sim.
## Enumerations
### ISO8583Version
Protocol version identifiers:
```python theme={null}
from iso8583sim.core.types import ISO8583Version
ISO8583Version.V1987 # Original version
ISO8583Version.V1993 # First revision
ISO8583Version.V2003 # Second revision
```
### FieldType
Field data types:
```python theme={null}
from iso8583sim.core.types import FieldType
FieldType.NUMERIC # n - Digits only (0-9)
FieldType.ALPHA # a - Letters only (A-Z, a-z)
FieldType.ALPHANUMERIC # an - Letters and numbers
FieldType.BINARY # b - Binary/hex data
FieldType.SPECIAL # s - Special characters
FieldType.TRACK2 # z - Track 2 magnetic stripe
FieldType.LLVAR # Variable length (max 99)
FieldType.LLLVAR # Variable length (max 999)
```
### CardNetwork
Supported card networks:
```python theme={null}
from iso8583sim.core.types import CardNetwork
CardNetwork.VISA
CardNetwork.MASTERCARD
CardNetwork.AMEX
CardNetwork.DISCOVER
CardNetwork.JCB
CardNetwork.UNIONPAY
```
### MessageClass
Message class identifiers (MTI position 2):
```python theme={null}
from iso8583sim.core.types import MessageClass
MessageClass.AUTHORIZATION # "1" - Authorization
MessageClass.FINANCIAL # "2" - Financial
MessageClass.FILE_ACTIONS # "3" - File action
MessageClass.REVERSAL # "4" - Reversal
MessageClass.RECONCILIATION # "5" - Reconciliation
MessageClass.ADMINISTRATIVE # "6" - Administrative
MessageClass.FEE_COLLECTION # "7" - Fee collection
MessageClass.NETWORK_MANAGEMENT # "8" - Network management
```
### MessageFunction
Message function identifiers (MTI position 3):
```python theme={null}
from iso8583sim.core.types import MessageFunction
MessageFunction.REQUEST # "0" - Request
MessageFunction.RESPONSE # "1" - Response
MessageFunction.ADVICE # "2" - Advice
MessageFunction.ADVICE_RESPONSE # "3" - Response to advice
MessageFunction.NOTIFICATION # "4" - Notification
```
## Data Classes
### FieldDefinition
Defines the structure of an ISO 8583 field:
```python theme={null}
from iso8583sim.core.types import FieldDefinition, FieldType
field = FieldDefinition(
field_type=FieldType.NUMERIC,
max_length=12,
description="Transaction Amount",
field_number=4,
encoding="ascii",
min_length=None,
padding_char="0",
padding_direction="left",
)
```
**Attributes:**
| Attribute | Type | Description |
| - | - | - |
| `field_type` | FieldType | Data type |
| `max_length` | int | Maximum length |
| `description` | str | Human-readable description |
| `field_number` | int | Field number (1-128) |
| `encoding` | str | Character encoding (default: "ascii") |
| `min_length` | int | Minimum length (for variable fields) |
| `padding_char` | str | Padding character |
| `padding_direction` | str | "left" or "right" |
### ISO8583Message
Represents a parsed or constructed message:
```python theme={null}
from iso8583sim.core.types import ISO8583Message, CardNetwork, ISO8583Version
message = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111",
3: "000000",
4: "000000001000",
},
bitmap="7000000000000000",
secondary_bitmap=None,
network=CardNetwork.VISA,
version=ISO8583Version.V1987,
raw="",
emv_data=None,
)
```
**Attributes:**
| Attribute | Type | Description |
| - | - | - |
| `mti` | str | Message Type Indicator |
| `fields` | dict\[int, str] | Field number to value mapping |
| `bitmap` | str | Primary bitmap (hex) |
| `secondary_bitmap` | str | Secondary bitmap (hex) |
| `network` | CardNetwork | Detected card network |
| `version` | ISO8583Version | Protocol version |
| `raw` | str | Original raw message |
| `emv_data` | dict | Parsed Field 55 EMV tags |
## Field Definitions
### Standard Fields (ISO8583\_FIELDS)
The `ISO8583_FIELDS` dictionary contains definitions for all 128 standard fields:
```python theme={null}
from iso8583sim.core.types import ISO8583_FIELDS
# Get field 2 definition
pan_field = ISO8583_FIELDS[2]
print(pan_field.description) # "Primary Account Number"
print(pan_field.field_type) # FieldType.LLVAR
print(pan_field.max_length) # 19
```
### Key Fields
| Field | Name | Type | Length | Description |
| - | - | - | - | - |
| 2 | PAN | LLVAR | 19 | Primary Account Number |
| 3 | Processing Code | n | 6 | Transaction type |
| 4 | Amount | n | 12 | Transaction amount |
| 7 | Transmission Date/Time | n | 10 | MMDDhhmmss |
| 11 | STAN | n | 6 | System Trace Audit Number |
| 12 | Local Time | n | 6 | hhmmss |
| 13 | Local Date | n | 4 | MMDD |
| 14 | Expiration Date | n | 4 | YYMM |
| 22 | POS Entry Mode | n | 3 | How card was read |
| 23 | Card Sequence Number | n | 3 | For multi-card PANs |
| 24 | NII | n | 3 | Network Identifier |
| 25 | POS Condition Code | n | 2 | Transaction condition |
| 35 | Track 2 Data | LLVAR | 37 | Magnetic stripe |
| 37 | Retrieval Reference | an | 12 | Unique reference |
| 38 | Authorization Code | an | 6 | Approval code |
| 39 | Response Code | an | 2 | 00=Approved |
| 41 | Terminal ID | ans | 8 | Card acceptor terminal |
| 42 | Merchant ID | ans | 15 | Card acceptor ID |
| 55 | ICC Data | LLLVAR | 999 | EMV chip data |
## Helper Functions
### get\_field\_definition
Get field definition with network/version overrides:
```python theme={null}
from iso8583sim.core.types import get_field_definition, CardNetwork
# Basic lookup
field_def = get_field_definition(2)
# With network-specific override
field_def = get_field_definition(62, network=CardNetwork.VISA)
```
### detect\_network
Detect card network from PAN:
```python theme={null}
from iso8583sim.core.types import detect_network
network = detect_network("4111111111111111")
print(network) # CardNetwork.VISA
network = detect_network("5500000000000004")
print(network) # CardNetwork.MASTERCARD
```
## Exceptions
### ParseError
Raised when parsing fails:
```python theme={null}
from iso8583sim.core.types import ParseError
try:
message = parser.parse(invalid_message)
except ParseError as e:
print(f"Parse error: {e}")
```
### BuildError
Raised when building fails:
```python theme={null}
from iso8583sim.core.types import BuildError
try:
raw = builder.build(invalid_message)
except BuildError as e:
print(f"Build error: {e}")
```
## API Reference
See [Core API Reference](../api/core#iso8583simcoretypes) for complete API documentation.
# Validator
Source: https://iso8583sim.com/docs/core/validator
The ISO8583Validator class validates ISO 8583 messages for structure and content correctness.
## Quick Start
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import ISO8583Message
validator = ISO8583Validator()
message = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111",
3: "000000",
4: "000000001000",
}
)
errors = validator.validate_message(message)
if errors:
for error in errors:
print(f"Error: {error}")
else:
print("Message is valid!")
```
## Validation Rules
### MTI Validation
* Must be exactly 4 digits
* First digit: ISO version (0, 1, 2)
* Second digit: Message class (1-8)
* Third digit: Message function (0-4, 8, 9)
* Fourth digit: Message origin (0-5)
```python theme={null}
# Valid MTIs
"0100" # Authorization request (1987)
"0110" # Authorization response
"0200" # Financial request
"0400" # Reversal request
# Invalid MTIs
"100" # Too short
"01A0" # Non-numeric
"0190" # Invalid function digit
```
### Field Type Validation
| Type | Rule | Example |
| - | - | - |
| Numeric (n) | Only digits 0-9 | `"123456"` |
| Alpha (a) | Only letters A-Z, a-z | `"APPROVED"` |
| Alphanumeric (an) | Letters and digits | `"ABC123"` |
| Binary (b) | Hex characters | `"9F2608"` |
| LLVAR | Max 99 chars | PAN field |
| LLLVAR | Max 999 chars | EMV data |
### Field Length Validation
```python theme={null}
# Fixed-length fields must match exactly
# Field 3 (Processing Code): 6 digits
"000000" # Valid
"00000" # Invalid (too short)
"0000000" # Invalid (too long)
# Variable-length fields checked against max
# Field 2 (PAN): LLVAR, max 19
"4111111111111111" # Valid (16 chars)
"41111111111111111111" # Invalid (20 chars)
```
### Network-Specific Validation
When network is specified, validates required fields:
```python theme={null}
from iso8583sim.core.types import CardNetwork
message.network = CardNetwork.VISA
# VISA requires: 2, 3, 4, 11, 14, 22, 24, 25
errors = validator.validate_message(message)
```
**Network Required Fields:**
| Network | Required Fields |
| - | - |
| VISA | 2, 3, 4, 11, 14, 22, 24, 25 |
| Mastercard | 2, 3, 4, 11, 22, 24, 25 |
| AMEX | 2, 3, 4, 11, 22, 25 |
| Discover | 2, 3, 4, 11, 22 |
| JCB | 2, 3, 4, 11, 22, 25 |
| UnionPay | 2, 3, 4, 11, 22, 25, 49 |
### Checking Against Other Networks
`validate_for_network` checks a message against another network's rules without changing it. `validate_for_networks` checks several at once, or every network when you pass no list:
```python theme={null}
from iso8583sim.core.types import CardNetwork
errors = validator.validate_for_network(message, CardNetwork.MASTERCARD)
results = validator.validate_for_networks(message) # {CardNetwork.VISA: [], CardNetwork.UNIONPAY: [...], ...}
failing = [net.value for net, errs in results.items() if errs]
```
### PAN Validation
Luhn checksum validation for Field 2:
```python theme={null}
"4111111111111111" # Valid (passes Luhn)
"4111111111111112" # Invalid (fails Luhn)
```
## Single Field Validation
```python theme={null}
from iso8583sim.core.types import get_field_definition
field_def = get_field_definition(2) # PAN field
is_valid, error = validator.validate_field(
field_number=2,
value="4111111111111111",
field_def=field_def
)
if not is_valid:
print(f"Field 2 error: {error}")
```
## Validation Examples
### Valid Authorization Request
```python theme={null}
message = ISO8583Message(
mti="0100",
network=CardNetwork.VISA,
fields={
0: "0100",
2: "4111111111111111", # PAN
3: "000000", # Processing Code
4: "000000001000", # Amount
11: "123456", # STAN
14: "2512", # Expiry
22: "051", # POS Entry Mode
24: "100", # NII
25: "00", # POS Condition Code
}
)
errors = validator.validate_message(message)
# errors = [] (valid)
```
### Invalid Message Examples
```python theme={null}
# Missing required field
message.fields.pop(4) # Remove amount
errors = validator.validate_message(message)
# ["Missing required field: 4"]
# Invalid field type
message.fields[4] = "ABCDEF" # Amount should be numeric
errors = validator.validate_message(message)
# ["Field 4 must contain only digits"]
# Invalid PAN (Luhn)
message.fields[2] = "4111111111111112"
errors = validator.validate_message(message)
# ["Field 2 (PAN) failed Luhn check"]
```
## Cython Acceleration
Validation functions have Cython optimizations:
| Function | Speedup |
| - | - |
| `is_numeric()` | \~2x |
| `is_alphanumeric()` | \~2x |
| `is_valid_hex()` | \~2x |
| `validate_pan_luhn()` | \~3x |
Detection is automatic when Cython extensions are compiled.
## API Reference
See [Core API Reference](../api/core#iso8583simcorevalidator) for complete API documentation.
# Wire Formats
Source: https://iso8583sim.com/docs/core/wire
The rest of iso8583sim works with messages as strings: an ASCII MTI, a hex bitmap, and binary fields as hex.
Real hosts exchange bytes, often with a binary bitmap, packed BCD numbers or EBCDIC text. `iso8583sim.wire` converts between the two.
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.samples import sample_message
from iso8583sim.wire import WireFormat
data = ISO8583Builder().build_bytes(sample_message(), WireFormat.bcd()) # bytes, 63 long
message = ISO8583Parser().parse_bytes(data, WireFormat.bcd()) # ISO8583Message
```
Decoded messages have the same field values the string parser gives, so validation, explain, conversion and the rest work on them unchanged.
The same functions are available directly as `iso8583sim.wire.encode_message` and `decode_message`.
## Presets
| Preset | MTI | Bitmap | Lengths | Numbers | Text | Binary fields | Typical use |
| - | - | - | - | - | - | - | - |
| `WireFormat.ascii_hex()` | ASCII | hex text | ASCII | ASCII | ASCII | hex text | Exactly `build()` output as bytes |
| `WireFormat.ascii_binary()` (default) | ASCII | binary | ASCII | ASCII | ASCII | raw | Many host links |
| `WireFormat.bcd()` | BCD | binary | BCD | BCD | ASCII | raw | POS terminal links |
| `WireFormat.ebcdic()` | EBCDIC | binary | EBCDIC | EBCDIC | EBCDIC | raw | IBM mainframe hosts |
`WireFormat.preset("bcd")` looks a preset up by name.
## Custom formats
Every part is configurable:
```python theme={null}
from iso8583sim.wire import Encoding, WireFormat
fmt = WireFormat(
mti=Encoding.BCD,
bitmap="binary", # or "hex"
length_prefix=Encoding.BINARY, # ASCII, EBCDIC, BCD or BINARY
numeric=Encoding.BCD, # ASCII, EBCDIC or BCD
text=Encoding.ASCII, # ASCII or EBCDIC
binary_fields="raw", # or "hex"
ebcdic_codec="cp037", # cp037 (US/Canada) or cp500 (international)
bcd_odd_padding="right", # odd-length variable BCD: "left" (leading 0) or "right" (trailing F)
)
```
Rules worth knowing:
* **BCD packs two digits per byte.** An LLVAR BCD length is one byte, an LLLVAR length two bytes. The length counts digits, not bytes.
* **Odd digit counts** need a filler nibble. Fixed numeric fields always get a leading `0`. For variable fields, networks differ, so `bcd_odd_padding` chooses a leading `0` or a trailing `F`.
* **Track 2** (field 35) uses `=` as a separator, which BCD writes as the nibble `D`.
* **Content types.** Fields 2, 32, 33, 99 and 100 are numeric, fields 35 and 36 are track data, and field 55 (EMV) is binary. Other variable fields are text.
## Accuracy
The encodings are tested against the independent [pyiso8583](https://github.com/knovichikhin/pyiso8583) library configured the same way, and every preset round-trips every field type.
Length headers and TPDUs for sending over TCP are covered separately.
# ISO 8583 Concepts
Source: https://iso8583sim.com/docs/getting-started/concepts
ISO 8583 is the international standard for financial transaction card-originated messages.
This guide covers the fundamental concepts.
## Message Structure
An ISO 8583 message consists of three main parts:
```
┌─────────┬──────────┬────────────────┐
│ MTI │ Bitmap │ Data Fields │
│ (4 hex) │ (16 hex) │ (variable) │
└─────────┴──────────┴────────────────┘
```
### MTI (Message Type Indicator)
The MTI is a 4-digit code that identifies the message type:
| Position | Meaning | Values |
| - | - | - |
| 1 | ISO Version | 0=1987, 1=1993, 2=2003 |
| 2 | Message Class | 1=Auth, 2=Financial, 4=Reversal |
| 3 | Message Function | 0=Request, 1=Response, 2=Advice |
| 4 | Message Origin | 0=Acquirer, 2=Issuer |
**Common MTIs:**
| MTI | Description |
| - | - |
| 0100 | Authorization Request |
| 0110 | Authorization Response |
| 0200 | Financial Request |
| 0210 | Financial Response |
| 0400 | Reversal Request |
| 0410 | Reversal Response |
### Bitmap
The bitmap indicates which fields are present in the message. It's a 64-bit (8-byte) or 128-bit (16-byte) field:
* **Primary Bitmap**: Fields 1-64
* **Secondary Bitmap**: Fields 65-128 (if bit 1 is set)
```python theme={null}
# Example: Bitmap 7024058020C09000
# Binary: 0111 0000 0010 0100 0000 0101 1000 0000 ...
# Fields present: 2, 3, 4, 7, 11, 14, 22, 24, 25, ...
```
### Data Fields
ISO 8583 defines 128 standard fields. Each field has:
* **Field Number**: 1-128
* **Data Type**: Numeric, Alpha, Alphanumeric, Binary
* **Length**: Fixed or Variable (LLVAR/LLLVAR)
## Field Types
### Fixed-Length Fields
```python theme={null}
# Field 3: Processing Code - 6 numeric digits
processing_code = "000000" # Purchase
# Field 4: Amount - 12 numeric digits
amount = "000000001000" # $10.00 (in cents)
```
### Variable-Length Fields
Variable fields have a length prefix:
* **LLVAR**: 2-digit length prefix (max 99 characters)
* **LLLVAR**: 3-digit length prefix (max 999 characters)
```python theme={null}
# Field 2: PAN (LLVAR)
# "16" prefix + 16-digit PAN
pan_field = "164111111111111111"
# Field 35: Track 2 Data (LLLVAR)
# "037" prefix + 37 characters
track2 = "037411111111111111=2512101123400001"
```
## Key Fields
| Field | Name | Type | Description |
| - | - | - | - |
| 2 | PAN | LLVAR | Primary Account Number (card number) |
| 3 | Processing Code | n6 | Transaction type (000000=purchase) |
| 4 | Amount | n12 | Transaction amount in minor units |
| 7 | Transmission Date/Time | n10 | MMDDhhmmss |
| 11 | STAN | n6 | System Trace Audit Number |
| 22 | POS Entry Mode | n3 | How card was read |
| 35 | Track 2 Data | LLVAR | Magnetic stripe data |
| 38 | Authorization Code | an6 | Approval code |
| 39 | Response Code | an2 | 00=Approved, 05=Declined |
| 41 | Terminal ID | ans8 | Card acceptor terminal ID |
| 42 | Merchant ID | ans15 | Card acceptor ID |
| 55 | EMV Data | LLLVAR | Chip card ICC data (TLV format) |
## Response Codes (Field 39)
| Code | Meaning |
| - | - |
| 00 | Approved |
| 01 | Refer to issuer |
| 05 | Do not honor |
| 12 | Invalid transaction |
| 13 | Invalid amount |
| 14 | Invalid card number |
| 51 | Insufficient funds |
| 54 | Expired card |
| 55 | Incorrect PIN |
## Processing Codes (Field 3)
The 6-digit processing code has three parts:
| Digits | Meaning |
| - | - |
| 1-2 | Transaction Type |
| 3-4 | Account Type (From) |
| 5-6 | Account Type (To) |
**Transaction Types:**
| Code | Type |
| - | - |
| 00 | Purchase/Goods & Services |
| 01 | Cash Withdrawal |
| 09 | Purchase with Cash Back |
| 20 | Return/Refund |
| 30 | Balance Inquiry |
## EMV Data (Field 55)
Field 55 contains chip card data in TLV (Tag-Length-Value) format:
```
┌───────┬────────┬───────────────┐
│ Tag │ Length │ Value │
│ (1-3) │ (1-3) │ (variable) │
└───────┴────────┴───────────────┘
```
**Common EMV Tags:**
| Tag | Name |
| - | - |
| 9F26 | Application Cryptogram |
| 9F27 | Cryptogram Information Data |
| 9F10 | Issuer Application Data |
| 9F37 | Unpredictable Number |
| 9F36 | Application Transaction Counter |
| 82 | Application Interchange Profile |
| 9C | Transaction Type |
## Card Networks
Different card networks have specific requirements:
* **VISA**: Field 62 (VISA private data), specific response codes
* **Mastercard**: Field 48 (additional data), DE127 subfields
* **AMEX**: Different field formats, 15-digit PANs
* **Discover**: Similar to VISA
* **JCB**: Japan-specific requirements
* **UnionPay**: China-specific requirements
See the [Networks Guide](../networks) for details.
## Next Steps
* [Parser Guide](../core/parser) - Parse ISO 8583 messages
* [Builder Guide](../core/builder) - Build messages
* [EMV Data Guide](../core/emv) - Work with chip card data
# Installation
Source: https://iso8583sim.com/docs/getting-started/installation
Install iso8583sim from PyPI:
## Requirements
* Python 3.10 or higher
* pip package manager
## Basic Installation
Install iso8583sim from PyPI:
```bash theme={null}
pip install iso8583sim
```
## Optional Dependencies
### LLM Providers
For AI-powered message explanation and generation:
```bash theme={null}
# Anthropic (Claude) - Recommended
pip install iso8583sim[anthropic]
# OpenAI (GPT)
pip install iso8583sim[openai]
# Google (Gemini)
pip install iso8583sim[google]
# Ollama (Local models)
pip install iso8583sim[ollama]
# All LLM providers
pip install iso8583sim[llm]
```
After installation, set your API key:
```bash theme={null}
export ANTHROPIC_API_KEY="sk-ant-..."
# or
export OPENAI_API_KEY="sk-..."
# or
export GOOGLE_API_KEY="..."
```
### MCP Server
To use iso8583sim from Claude, Cursor or another MCP client:
```bash theme={null}
pip install iso8583sim[mcp]
```
See [MCP Server](../mcp-server) for client setup.
### REST API
```bash theme={null}
pip install iso8583sim[web]
```
See [REST API](../web).
### PIN Blocks and MACs
```bash theme={null}
pip install iso8583sim[security]
```
See [PIN Blocks and MACs](../core/security).
### Performance Extensions
The compiled Cython extensions (about 2x faster parsing) are included in the wheels on PyPI for CPython 3.10 to 3.14 on:
* Linux: x86\_64 and aarch64 (glibc and musl)
* macOS: Intel and Apple Silicon
* Windows: x64
On other platforms pip installs the pure-Python wheel, which works the same, only slower. It also runs in Pyodide (Python in the browser, and Cloudflare Python Workers). To compile the extensions yourself, install a C compiler and run `pip install --no-binary iso8583sim iso8583sim`. To check which you have:
```bash theme={null}
python -c "import iso8583sim.core.parser as p; print(p._USE_CYTHON)"
```
### Development
For development and testing:
```bash theme={null}
pip install iso8583sim[dev]
```
This includes pytest, ruff, mypy, and pre-commit hooks.
### Documentation
The docs are built with [Mintlify](https://mintlify.com) from the `docs/` folder. To preview them locally (needs Node.js 20.17 or newer):
```bash theme={null}
cd docs
npx mint dev
```
The Python API reference and the REST API spec are generated from the code. After changing docstrings or endpoints, regenerate them:
```bash theme={null}
pip install -e ".[docs,web]"
python scripts/generate_api_docs.py
python scripts/export_openapi.py
```
## Development Installation
Clone and install in development mode:
```bash theme={null}
git clone https://github.com/bassrehab/ISO8583-Simulator.git
cd iso8583sim
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
## Verify Installation
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.builder import ISO8583Builder
print("iso8583sim installed successfully!")
```
## Next Steps
* [Quick Start Tutorial](./quickstart) - Build your first message
* [ISO 8583 Concepts](./concepts) - Learn the basics
# Quick Start
Source: https://iso8583sim.com/docs/getting-started/quickstart
This guide will get you parsing and building ISO 8583 messages in under 5 minutes.
## Building Your First Message
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.types import ISO8583Message
# Create a builder
builder = ISO8583Builder()
# Define a message
message = ISO8583Message(
mti="0100", # Authorization request
fields={
0: "0100", # MTI
2: "4111111111111111", # PAN (Primary Account Number)
3: "000000", # Processing Code (purchase)
4: "000000001000", # Amount ($10.00)
11: "123456", # STAN (System Trace Audit Number)
41: "TERM0001", # Terminal ID
42: "MERCHANT123456 ", # Merchant ID
}
)
# Build the raw message
raw_message = builder.build(message)
print(f"Raw message: {raw_message}")
```
## Parsing a Message
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
parser = ISO8583Parser()
# Parse the raw message
parsed = parser.parse(raw_message)
print(f"MTI: {parsed.mti}")
print(f"PAN: {parsed.fields.get(2)}")
print(f"Amount: {parsed.fields.get(4)}")
```
## Validating a Message
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
validator = ISO8583Validator()
# Validate the message
errors = validator.validate_message(parsed)
if errors:
print("Validation errors:")
for error in errors:
print(f" - {error}")
else:
print("Message is valid!")
```
## Using Demo Helpers
The `demo` module provides convenient functions for generating test messages:
```python theme={null}
from iso8583sim.demo import generate_auth_request, pretty_print
# Generate an authorization request
auth_msg = generate_auth_request(
pan="4111111111111111",
amount=10000, # $100.00 in cents
terminal_id="TERM0001",
merchant_id="GASSTATION12345",
)
# Pretty print the message
pretty_print(auth_msg)
```
## EMV/Chip Card Messages
```python theme={null}
from iso8583sim.demo import generate_emv_auth
# Generate an EMV authorization
emv_msg = generate_emv_auth(
pan="4111111111111111",
amount=25000, # $250.00
cryptogram="1234567890ABCDEF",
)
pretty_print(emv_msg)
```
## CLI Usage
Parse a message from the command line:
```bash theme={null}
iso8583sim parse "0100702406C120E09000..."
```
Build a message:
```bash theme={null}
iso8583sim build --mti 0100 --fields fields.json
```
Validate a message:
```bash theme={null}
iso8583sim validate "0100702406C120E09000..."
```
## LLM Features
Explain a message in plain English:
```python theme={null}
from iso8583sim.llm import MessageExplainer
explainer = MessageExplainer()
explanation = explainer.explain(parsed)
print(explanation)
```
Generate a message from natural language:
```python theme={null}
from iso8583sim.llm import MessageGenerator
generator = MessageGenerator()
message = generator.generate("$50 VISA purchase at a coffee shop")
```
## Next Steps
* [ISO 8583 Concepts](./concepts) - Learn about MTI, bitmaps, and fields
* [Parser Guide](../core/parser) - Deep dive into parsing
* [Builder Guide](../core/builder) - Advanced message building
* [Network Support](../networks) - VISA, Mastercard, and more
# Upgrading
Source: https://iso8583sim.com/docs/getting-started/upgrading
The string-based API (ISO8583Parser.parse, ISO8583Builder.build, ISO8583Validator) has not changed since 1.0.
Code written for 1.x keeps working. This page lists the changes in behaviour and dependencies that can still affect an upgrade.
## From 1.1.x
### Dependencies (1.2.0)
* **`fastapi` and `uvicorn` are no longer installed by default.** If your code imports them through iso8583sim's dependencies, install the `web` extra: `pip install iso8583sim[web]`.
* **The `anthropic` extra requires `anthropic>=1.0.0`.**
* **The `google` extra installs `google-genai`** instead of the deprecated `google-generativeai`. If you imported `google.generativeai` yourself, install it separately.
### LLM defaults (1.2.0)
Default models changed to current ones: Anthropic `claude-opus-5`, OpenAI `gpt-6-astra`, Google `gemini-3.8-flash` (Ollama is unchanged). The new defaults cost more per token than the old ones. To keep costs down, pass a model explicitly:
```python theme={null}
MessageExplainer(provider=get_provider("anthropic", model="claude-sonnet-5"))
```
or `--model` on the CLI. Default output limits rose from 4096 to 16000 tokens so reasoning models aren't cut off.
Refusals now raise `LLMError` instead of returning an empty string. If you relied on an empty result, catch `LLMError`.
### Behaviour fixes you might notice
| Version | Change | What to check |
| - | - | - |
| 1.2.0 | `create_reversal` fills field 90 with the original STAN, time and institution IDs (the STAN used to be blank). | Tests that asserted the old field 90 value. |
| 1.2.0 | The parser detects Discover and Mastercard 2-series cards. | Code that expected `network=None` for those PANs. |
| 1.3.0 | MTIs starting with `2` (ISO 8583:2003) are valid. | Tests that expected `2xxx` MTIs to be rejected. |
| 1.3.0 | `iso8583sim validate` exits 1 without printing "Error validating message" when a message is invalid. | Scripts that matched that text. |
| 1.3.1 | `iso8583sim generate --amount` is in minor units (`1000` is 10.00). It used to scale amounts by 10,000 (or 100 for decimals). | Messages generated by earlier versions have wrong amounts. |
## From 1.4.x to 1.5
**Network validation changed.** Check VISA and Mastercard test data against the new rules:
* **Stricter:** field 22 must use a known PAN entry mode (first two digits) and PIN entry capability (third digit). Mastercard rejects the Visa-only mode `95`. Mastercard field 48 must be a transaction category code followed by well-formed subelements, so values such as `MC123` are now rejected. See [Field 22](../networks#field-22-pos-entry-mode).
* **Looser:** VISA field 44 no longer has to be hexadecimal, and Mastercard field 48 no longer has to start with `MC`. Both rules were wrong.
## New in 1.4
Additive only. See the changelog for details.
* [Wire formats](../core/wire): encode and decode bytes with binary bitmaps, BCD and EBCDIC (`build_bytes`, `parse_bytes`).
* [Networking](../net): TCP client, mock issuer host, and the `serve`, `send` and `load` commands.
* MCP `send_to_host` tool, limited to allowed hosts.
# iso8583sim
Source: https://iso8583sim.com/docs/index
Parse, build, validate and simulate ISO 8583 payment messages in Python.
iso8583sim is a toolkit for card payment messages. It covers the 1987, 1993 and 2003 versions of ISO 8583 and the VISA, Mastercard, AMEX, Discover, JCB and UnionPay networks, from a single message up to a mock issuer host under load.
```bash theme={null}
pip install iso8583sim
```
```python theme={null}
from iso8583sim.core.builder import ISO8583Builder
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.samples import sample_message
# A VISA authorization request for 25.00
raw = ISO8583Builder().build(sample_message(amount_minor_units=2500))
message = ISO8583Parser().parse(raw)
print(message.mti, message.network, message.fields[4])
# 0100 CardNetwork.VISA 000000002500
```
## Start here
Build, parse and validate your first messages in a few minutes.
MTIs, bitmaps and data elements, explained with examples.
## Work with messages
The core API, with network-aware validation and EMV (field 55) support.
What each network requires, and the field 22 and field 48 rules the validator enforces.
ISO 9564 PIN blocks and ISO 9797-1 MACs for fields 52, 64 and 128.
Convert messages between the 1987, 1993 and 2003 versions.
## Simulate a network
Binary bitmaps, packed BCD and EBCDIC, byte for byte.
Run a rules-driven issuer, send over TCP and measure throughput.
## Other ways in
Parse, explain, convert, serve and load test from the shell.
Every feature over HTTP, with an interactive playground.
Let Claude, Cursor and other assistants work with messages.
## Performance
With the compiled extensions (included in the wheels for Linux, macOS and Windows), iso8583sim parses about 180,000 messages per second on Apple Silicon. See [Performance](./performance) for the numbers and tuning.
iso8583sim is developed on [GitHub](https://github.com/bassrehab/ISO8583-Simulator) and dual-licensed: free under the [GNU AGPL-3.0](https://github.com/bassrehab/ISO8583-Simulator/blob/main/LICENSE), or under a [commercial license](https://github.com/bassrehab/ISO8583-Simulator/blob/main/COMMERCIAL-LICENSE.md) for closed-source products and services.
# Message Explainer
Source: https://iso8583sim.com/docs/llm/explainer
The MessageExplainer class uses LLMs to explain ISO 8583 messages in plain English.
## Quick Start
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.llm import MessageExplainer
# Parse a message
parser = ISO8583Parser()
message = parser.parse(raw_message)
# Create explainer
explainer = MessageExplainer()
# Get explanation
explanation = explainer.explain(message)
print(explanation)
```
## Explaining Messages
### From Message Object
```python theme={null}
from iso8583sim.demo import generate_auth_request
from iso8583sim.llm import MessageExplainer
message = generate_auth_request(
pan="4111111111111111",
amount=10000,
terminal_id="TERM0001",
)
explainer = MessageExplainer()
explanation = explainer.explain(message)
print(explanation)
```
Output:
```
This is an authorization request (MTI 0100) for a $100.00 purchase using a
VISA card ending in 1111. The transaction originated from terminal TERM0001
at a merchant location. The card was read using chip (EMV) technology with
no PIN entry required.
Key fields:
- PAN: 4111111111111111 (VISA)
- Amount: $100.00
- Processing Code: 000000 (Purchase)
- POS Entry Mode: 051 (Chip, no PIN)
```
### From Raw Message String
```python theme={null}
raw_message = "0100702406C120E09000..."
explanation = explainer.explain(raw_message)
print(explanation)
```
### Verbose Mode
Get detailed field-by-field explanations:
```python theme={null}
explanation = explainer.explain(message, verbose=True)
print(explanation)
```
Output:
```
## Message Overview
Authorization Request (MTI 0100) from an acquirer...
## Field Details
### Field 2 - Primary Account Number (PAN)
Value: 4111111111111111
This is a VISA card (prefix 4) with 16 digits...
### Field 3 - Processing Code
Value: 000000
Indicates a purchase transaction...
### Field 4 - Amount
Value: 000000010000
Transaction amount of $100.00 (in minor units)...
[Additional fields...]
```
## Explaining Specific Elements
### Response Codes
```python theme={null}
code_explanation = explainer.explain_response_code("51")
print(code_explanation)
```
Output:
```
Response Code 51: Insufficient Funds
The cardholder's account does not have enough available balance to
complete this transaction. The issuing bank declined the authorization.
Recommended actions:
- Customer should use a different payment method
- Customer could deposit funds if using debit
- Merchant should not force or retry the transaction
```
### Individual Fields
```python theme={null}
field_explanation = explainer.explain_field(22, "051")
print(field_explanation)
```
Output:
```
Field 22 - POS Entry Mode: 051
This indicates:
- Position 1-2 (05): Chip card read - The card's EMV chip was used
- Position 3 (1): PIN not entered
The transaction used secure chip technology, which provides better
fraud protection than magnetic stripe. No PIN was required, likely
because the amount was below the CVM (Cardholder Verification Method)
limit or the terminal doesn't support PIN entry.
```
## EMV Data Explanation
```python theme={null}
from iso8583sim.demo import generate_emv_auth
emv_message = generate_emv_auth(
pan="4111111111111111",
amount=25000,
cryptogram="1234567890ABCDEF",
)
explanation = explainer.explain(emv_message, verbose=True)
print(explanation)
```
Output includes EMV tag explanations:
```
## Field 55 - ICC System Related Data (EMV)
Tag 9F26 (Application Cryptogram): 1234567890ABCDEF
- The cryptogram generated by the chip for this transaction
- Used by the issuer to verify transaction authenticity
Tag 9F27 (Cryptogram Information Data): 80
- Value 80 indicates ARQC (Authorization Request Cryptogram)
- The card is requesting online authorization from the issuer
[Additional EMV tags...]
```
## Custom Provider
```python theme={null}
from iso8583sim.llm import MessageExplainer, get_provider
# Use a specific provider
provider = get_provider("openai")
explainer = MessageExplainer(provider=provider)
# Use a specific model
from iso8583sim.llm.providers.anthropic import AnthropicProvider
provider = AnthropicProvider(model="claude-3-haiku-20240307")
explainer = MessageExplainer(provider=provider)
```
## Batch Explanation
```python theme={null}
messages = [parse(raw) for raw in raw_messages]
explanations = []
for message in messages:
explanation = explainer.explain(message)
explanations.append(explanation)
```
## Error Handling
```python theme={null}
from iso8583sim.llm import MessageExplainer, LLMError
explainer = MessageExplainer()
try:
explanation = explainer.explain(message)
except LLMError as e:
print(f"Failed to explain message: {e}")
# Fall back to structured output
for field_num, value in message.fields.items():
print(f"Field {field_num}: {value}")
```
## API Reference
See [LLM API Reference](../api/llm#iso8583simllmexplainer) for complete API documentation.
# Message Generator
Source: https://iso8583sim.com/docs/llm/generator
The MessageGenerator class uses LLMs to create ISO 8583 messages from natural language descriptions.
## Quick Start
```python theme={null}
from iso8583sim.llm import MessageGenerator
generator = MessageGenerator()
# Generate from natural language
message = generator.generate("$50 VISA purchase at a coffee shop")
print(f"MTI: {message.mti}")
print(f"PAN: {message.fields.get(2)}")
print(f"Amount: {message.fields.get(4)}")
```
## Generating Messages
### Simple Purchase
```python theme={null}
message = generator.generate("$100 purchase with Mastercard")
# Result:
# MTI: 0100
# Field 2: 5500000000000004
# Field 3: 000000
# Field 4: 000000010000
```
### Specific Scenarios
```python theme={null}
# ATM withdrawal
message = generator.generate("$200 ATM cash withdrawal with VISA card")
# Refund
message = generator.generate("Refund $25 to card ending in 4444")
# Balance inquiry
message = generator.generate("Balance inquiry at ATM for debit card")
# International transaction
message = generator.generate("$500 purchase in EUR at a Paris hotel")
```
### With Validation
By default, generated messages are validated:
```python theme={null}
# With validation (default)
message = generator.generate("$50 purchase", validate=True)
# Skip validation (faster, for testing)
message = generator.generate("$50 purchase", validate=False)
```
## Suggesting Missing Fields
Complete a partial message:
```python theme={null}
from iso8583sim.core.types import ISO8583Message
# Create partial message
partial = ISO8583Message(
mti="0100",
fields={
0: "0100",
2: "4111111111111111",
4: "000000010000",
}
)
# Get suggestions for missing fields
suggestions = generator.suggest_fields(partial)
print("Suggested fields:")
for field_num, value in suggestions.items():
print(f" Field {field_num}: {value}")
```
Output:
```
Suggested fields:
Field 3: 000000
Field 7: 1225120000
Field 11: 123456
Field 14: 2512
Field 22: 051
Field 41: TERM0001
Field 42: MERCHANT123456
```
## Generation Examples
### Authorization Request
```python theme={null}
message = generator.generate(
"Authorization request for $250 EMV chip transaction at gas station, "
"VISA card ending in 1234"
)
```
### Financial Transaction
```python theme={null}
message = generator.generate(
"Financial transaction (0200) for $75.50 at retail store, "
"contactless Mastercard payment"
)
```
### Reversal
```python theme={null}
message = generator.generate(
"Reversal for failed $100 transaction, original STAN 123456"
)
```
### Response Message
```python theme={null}
message = generator.generate(
"Approval response for authorization request, "
"response code 00, auth code A12345"
)
```
## Custom Provider
```python theme={null}
from iso8583sim.llm import MessageGenerator, get_provider
# Use a specific provider
provider = get_provider("openai")
generator = MessageGenerator(provider=provider)
# Use a specific model for faster generation
from iso8583sim.llm.providers.anthropic import AnthropicProvider
provider = AnthropicProvider(model="claude-3-haiku-20240307")
generator = MessageGenerator(provider=provider)
```
## Test Data Generation
Generate test datasets:
```python theme={null}
from iso8583sim.llm import MessageGenerator
from iso8583sim.core.builder import ISO8583Builder
generator = MessageGenerator()
builder = ISO8583Builder()
scenarios = [
"$100 VISA purchase at retail store",
"$50 Mastercard at restaurant with tip",
"$200 ATM withdrawal",
"Declined transaction, insufficient funds",
"EMV chip transaction at gas pump",
]
test_messages = []
for scenario in scenarios:
try:
message = generator.generate(scenario, validate=False)
raw = builder.build(message)
test_messages.append({
"scenario": scenario,
"message": message,
"raw": raw,
})
except Exception as e:
print(f"Failed to generate '{scenario}': {e}")
```
## Guidance for Better Results
Provide specific details for better generation:
```python theme={null}
# Less specific - may generate generic values
message = generator.generate("A purchase")
# More specific - generates accurate message
message = generator.generate(
"$150.00 purchase at electronics store, "
"VISA chip card (no PIN), "
"terminal ID ELEC0001, "
"merchant ID BESTBUY123456"
)
```
### Helpful Details to Include
* Transaction amount and currency
* Card network (VISA, Mastercard, etc.)
* Card entry mode (chip, swipe, contactless, manual)
* Transaction type (purchase, refund, withdrawal)
* Merchant type or name
* Terminal and merchant IDs
## Error Handling
```python theme={null}
from iso8583sim.llm import MessageGenerator, LLMError
from iso8583sim.core.types import BuildError
generator = MessageGenerator()
try:
message = generator.generate(description)
except LLMError as e:
print(f"LLM error: {e}")
except BuildError as e:
print(f"Generated message failed validation: {e}")
```
## Limitations
* Generated messages may not pass all network-specific validations
* Complex multi-message scenarios may need manual adjustment
* Generated PANs are test numbers, not real card numbers
* Some network-specific fields may be missing
## API Reference
See [LLM API Reference](../api/llm#iso8583simllmgenerator) for complete API documentation.
# LLM Features
Source: https://iso8583sim.com/docs/llm/index
iso8583sim includes AI-powered features for message explanation and generation using large language models.
## Overview
The LLM module provides two main capabilities:
* **MessageExplainer**: Explain ISO 8583 messages in plain English
* **MessageGenerator**: Generate messages from natural language descriptions
## Installation
LLM features require an LLM provider. Install your preferred provider:
```bash theme={null}
# Anthropic (Claude) - Recommended
pip install iso8583sim[anthropic]
# OpenAI (GPT)
pip install iso8583sim[openai]
# Google (Gemini)
pip install iso8583sim[google]
# Ollama (Local models)
pip install iso8583sim[ollama]
# All providers
pip install iso8583sim[llm]
```
## Configuration
Set your API key as an environment variable:
```bash theme={null}
# Anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
# OpenAI
export OPENAI_API_KEY="sk-..."
# Google
export GOOGLE_API_KEY="..."
# Ollama (no key needed for local)
```
## Quick Start
### Explain a Message
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.llm import MessageExplainer
# Parse a message
parser = ISO8583Parser()
message = parser.parse(raw_message)
# Create explainer (auto-detects provider)
explainer = MessageExplainer()
# Get plain English explanation
explanation = explainer.explain(message)
print(explanation)
```
### Generate a Message
```python theme={null}
from iso8583sim.llm import MessageGenerator
generator = MessageGenerator()
# Generate from natural language
message = generator.generate("$50 VISA purchase at a coffee shop")
print(f"MTI: {message.mti}")
print(f"Amount: {message.fields.get(4)}")
```
## Provider Auto-Detection
When no provider is specified, the first available provider is used:
1. Anthropic (if installed and configured)
2. OpenAI (if installed and configured)
3. Google (if installed and configured)
4. Ollama (if installed and running)
```python theme={null}
from iso8583sim.llm import list_available_providers, list_installed_providers
# See what's installed
print(list_installed_providers()) # ['anthropic', 'openai']
# See what's configured (has API key)
print(list_available_providers()) # ['anthropic']
```
## Module Structure
```
iso8583sim.llm/
├── __init__.py # Public API exports
├── base.py # Provider interface
├── explainer.py # MessageExplainer
├── generator.py # MessageGenerator
└── providers/
├── __init__.py # Provider factory
├── anthropic.py # Claude provider
├── openai.py # GPT provider
├── google.py # Gemini provider
└── ollama.py # Ollama provider
```
## Interactive Notebooks
Explore LLM features with our Jupyter notebooks:
| Notebook | Description |
| - | - |
| [07\_llm\_features.ipynb](https://github.com/bassrehab/ISO8583-Simulator/blob/main/notebooks/07_llm_features.ipynb) | LLM features with OpenAI (cloud API) |
| [08\_llm\_features\_ollama.ipynb](https://github.com/bassrehab/ISO8583-Simulator/blob/main/notebooks/08_llm_features_ollama.ipynb) | LLM features with Ollama (local, offline, private) |
## Next Steps
* [Providers](./providers) - Configure and customize LLM providers
* [Message Explainer](./explainer) - Detailed explanation features
* [Message Generator](./generator) - Advanced generation options
# LLM Providers
Source: https://iso8583sim.com/docs/llm/providers
iso8583sim supports multiple LLM providers with a unified interface.
## Available Providers
| Provider | Package | Model Default | Env Variable |
| - | - | - | - |
| Anthropic | `anthropic` | claude-opus-5 | `ANTHROPIC_API_KEY` |
| OpenAI | `openai` | gpt-6-astra | `OPENAI_API_KEY` |
| Google | `google-genai` | gemini-3.8-flash | `GOOGLE_API_KEY` |
| Ollama | `ollama` | llama3.2 | (none) |
## Installation
```bash theme={null}
# Single provider
pip install iso8583sim[anthropic]
pip install iso8583sim[openai]
pip install iso8583sim[google]
pip install iso8583sim[ollama]
# All providers
pip install iso8583sim[llm]
```
## Configuration
### Environment Variables
```bash theme={null}
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export GOOGLE_API_KEY="..."
```
### Check Availability
```python theme={null}
from iso8583sim.llm import list_installed_providers, list_available_providers
# Packages installed
print(list_installed_providers())
# ['anthropic', 'openai']
# Installed + configured (has API key)
print(list_available_providers())
# ['anthropic']
```
## Provider Factory
### Auto-Detection
```python theme={null}
from iso8583sim.llm import get_provider
# Auto-detect first available provider
provider = get_provider()
print(f"Using: {provider.name}") # "Anthropic"
```
### Specific Provider
```python theme={null}
from iso8583sim.llm import get_provider
# Request specific provider
provider = get_provider("openai")
provider = get_provider("anthropic")
provider = get_provider("google")
provider = get_provider("ollama")
```
### Custom Model
```python theme={null}
from iso8583sim.llm import get_provider
# Use a specific model
provider = get_provider("anthropic", model="claude-3-haiku-20240307")
provider = get_provider("openai", model="gpt-6-luna")
```
## Direct Provider Usage
### Anthropic
```python theme={null}
from iso8583sim.llm.providers.anthropic import AnthropicProvider
provider = AnthropicProvider(
api_key="sk-ant-...", # Or use env var
model="claude-opus-5",
max_tokens=16000,
)
response = provider.complete("Explain ISO 8583 Field 55")
print(response)
```
With `claude-opus-5` (and Claude Fable 5 / 5.1), requests opt into Anthropic's server-side refusal fallbacks: if a safety classifier declines a request, the API retries it on Anthropic's recommended fallback model. If the whole chain declines, `complete()` raises `LLMError`.
### OpenAI
```python theme={null}
from iso8583sim.llm.providers.openai import OpenAIProvider
provider = OpenAIProvider(
api_key="sk-...", # Or use env var
model="gpt-6-astra",
max_tokens=16000, # Sent as max_completion_tokens, which includes reasoning tokens
)
response = provider.complete("Explain ISO 8583 Field 55")
print(response)
```
### Google
```python theme={null}
from iso8583sim.llm.providers.google import GoogleProvider
provider = GoogleProvider(
api_key="...", # Or use env var
model="gemini-3.8-flash",
)
response = provider.complete("Explain ISO 8583 Field 55")
```
### Ollama (Local)
Ollama runs completely locally - no API keys, no data sent externally, works offline.
**Setup:**
```bash theme={null}
# Install Ollama: https://ollama.com
ollama serve # Start the server
ollama pull llama3.1 # Pull a model
ollama pull qwen3-coder # Or a coding-focused model
```
**Usage:**
```python theme={null}
from iso8583sim.llm.providers.ollama import OllamaProvider
# Default model (llama3.2)
provider = OllamaProvider()
# Specific model
provider = OllamaProvider(
model="qwen3-coder:30b",
host="http://localhost:11434",
)
response = provider.complete("Explain ISO 8583 Field 55")
```
**Recommended Models:**
| Model | Size | Best For |
| - | - | - |
| `llama3.1` | 8B | General purpose, fast |
| `qwen3-coder` | 30B | Code-focused, detailed explanations |
| `mistral` | 7B | Fast, good quality |
| `devstral-small-2` | 24B | Coding tasks |
See the [Ollama notebook](https://github.com/bassrehab/ISO8583-Simulator/blob/main/notebooks/08_llm_features_ollama.ipynb) for working examples.
## Using with Explainer/Generator
```python theme={null}
from iso8583sim.llm import MessageExplainer, get_provider
# Auto-detect
explainer = MessageExplainer()
# Specific provider
provider = get_provider("anthropic")
explainer = MessageExplainer(provider=provider)
# Custom model
from iso8583sim.llm.providers.anthropic import AnthropicProvider
provider = AnthropicProvider(model="claude-3-haiku-20240307")
explainer = MessageExplainer(provider=provider)
```
## Response Metadata
Get usage information with `complete_with_metadata`:
```python theme={null}
from iso8583sim.llm import get_provider
provider = get_provider()
response = provider.complete_with_metadata("Explain ISO 8583")
print(f"Content: {response.content}")
print(f"Model: {response.model}")
print(f"Provider: {response.provider}")
print(f"Input tokens: {response.usage['input_tokens']}")
print(f"Output tokens: {response.usage['output_tokens']}")
```
## Error Handling
```python theme={null}
from iso8583sim.llm import (
get_provider,
LLMError,
ProviderConfigError,
ProviderNotAvailableError,
)
try:
provider = get_provider("anthropic")
response = provider.complete("...")
except ProviderNotAvailableError as e:
print(f"Provider not installed: {e}")
except ProviderConfigError as e:
print(f"Provider not configured: {e}")
except LLMError as e:
print(f"API error: {e}")
```
## Provider Interface
All providers implement `LLMProvider`:
```python theme={null}
from abc import ABC, abstractmethod
class LLMProvider(ABC):
@property
@abstractmethod
def name(self) -> str:
"""Provider name (e.g., 'Anthropic')"""
pass
@property
@abstractmethod
def model(self) -> str:
"""Model being used"""
pass
@abstractmethod
def complete(self, prompt: str, system: str | None = None) -> str:
"""Send prompt and return response text"""
pass
@abstractmethod
def complete_with_metadata(
self, prompt: str, system: str | None = None
) -> LLMResponse:
"""Send prompt and return response with metadata"""
pass
```
## API Reference
See [LLM API Reference](../api/llm#iso8583simllmproviders) for complete API documentation.
# MCP Server
Source: https://iso8583sim.com/docs/mcp-server/index
iso8583sim includes an MCP (Model Context Protocol) server.
It lets AI assistants such as Claude, Cursor and other MCP clients parse, build, validate and explain ISO 8583 messages directly.
With the server connected you can ask things like:
* "Explain this message: `0100702405800...`"
* "Why was this transaction declined?"
* "Generate a Mastercard authorization for \$25 and the matching approval response."
* "What's different between these two messages?"
## Installation
```bash theme={null}
pip install iso8583sim[mcp]
# To also use the PIN block and MAC tools
pip install "iso8583sim[mcp,security]"
```
The server runs over stdio:
```bash theme={null}
iso8583sim mcp
```
You don't normally run this yourself. Your MCP client starts it.
## Client Setup
### Claude Code
```bash theme={null}
claude mcp add iso8583sim -- iso8583sim mcp
```
To share the server with everyone working in a repository, add it to `.mcp.json` at the project root:
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "iso8583sim",
"args": ["mcp"]
}
}
}
```
### Claude Desktop
Open **Settings > Developer > Edit Config** and add the server to `claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "iso8583sim",
"args": ["mcp"]
}
}
}
```
Restart Claude Desktop after saving.
### Cursor
Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "iso8583sim",
"args": ["mcp"]
}
}
}
```
### Running without installing
With [uv](https://docs.astral.sh/uv/), a client can run the server without a separate install step:
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "uvx",
"args": ["--from", "iso8583sim[mcp]", "iso8583sim", "mcp"]
}
}
}
```
**Virtual environments.** If you installed iso8583sim in a virtual environment, use the full path to the command, for example `/path/to/.venv/bin/iso8583sim`. GUI apps such as Claude Desktop don't see your shell's `PATH`.
## Tools
| Tool | What it does |
| - | - |
| `parse_message` | Parse a raw message into its MTI, bitmap and named fields. Decodes field 55 when present. |
| `build_message` | Build a raw message from an MTI and a map of field numbers to values. |
| `validate_message` | Check format, length and network rules. Returns `valid` and a list of errors. |
| `explain_message` | Plain-language summary: transaction type, amount, masked card, network, response code meaning. Rule-based, so no LLM key is needed. |
| `decode_emv` | Decode EMV TLV data (field 55) into named tags, with TVR and CID explanations. |
| `generate_test_message` | Generate a valid `auth` (0100), `financial` (0200) or `echo` (0800) message. Uses a standard test PAN for the network when none is given. |
| `create_response` | Create the response to a request (0100 to 0110, 0200 to 0210) with a chosen response code. |
| `create_reversal` | Create a reversal (04xx) with field 90 set to the original data elements. |
| `lookup_field` | Field definition (name, type, length, padding), including network and version overrides. |
| `detect_network` | Detect the card network from a PAN prefix. |
| `diff_messages` | Compare two messages and list added, removed and changed fields. |
| `convert_version` | Convert a message to another ISO 8583 version, listing dropped fields and notes. |
| `check_network_rules` | Check a message against every network's rules (or a chosen few) and report pass/fail for each. |
| `encrypt_pin_block` | Build an encrypted ISO 9564 PIN block (formats 0, 1, 3, 4) for field 52. Needs the `security` extra. |
| `decrypt_pin_block` | Recover the PIN from an encrypted PIN block. Needs the `security` extra. |
| `sign_message` | Add or replace a message's MAC in field 64 or 128 (ISO 9797-1 algorithm 1 or 3). Needs the `security` extra. |
| `verify_message_mac` | Check a message's MAC. Needs the `security` extra. |
| `send_to_host` | Send a message to an ISO 8583 host over TCP and return the parsed response. Only allowed hosts (see below). |
**Test keys only.** The PIN and MAC tools take keys and clear PINs as plain text, and anything you give the assistant is sent to your AI provider. Use them only with test keys and test PINs.
Conventions shared by all tools:
* Messages are ASCII strings: MTI, hex bitmap, then field data.
* Field numbers in `fields` are strings, for example `{"2": "4111111111111111", "4": "000000001000"}`.
* `version` is `"1987"` (default), `"1993"` or `"2003"`.
* `network` is optional: `VISA`, `MASTERCARD`, `AMEX`, `DISCOVER`, `JCB` or `UNIONPAY`. Most tools detect it from the PAN when you leave it out.
Invalid input returns a tool error that explains the problem, for example `Unknown network 'DINERS'` or `Invalid MTI format - must be numeric`.
### Allowed hosts for `send_to_host`
`send_to_host` only connects to hosts listed in the `ISO8583SIM_ALLOWED_HOSTS` environment variable of the MCP server, so an assistant can't be used to reach arbitrary machines. The default is this machine only. Entries are a host (any port) or `host:port`, comma separated:
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "iso8583sim",
"args": ["mcp"],
"env": {"ISO8583SIM_ALLOWED_HOSTS": "127.0.0.1,uat-switch.internal:9000"}
}
}
}
```
Pair it with `iso8583sim serve` for a local mock issuer. See [Networking](../net).
## Resources
| URI | Contents |
| - | - |
| `iso8583://fields/{version}` | Field definitions for a version |
| `iso8583://fields/{version}/{network}` | Field definitions with a network's overrides applied |
| `iso8583://codes/response` | Response codes (field 39) and their meanings |
| `iso8583://codes/processing` | Processing code transaction types (field 3) |
| `iso8583://emv/tags` | EMV tag dictionary |
## Prompts
| Prompt | Arguments | Purpose |
| - | - | - |
| `debug_declined_transaction` | `message` | Walks the assistant through explaining, validating and decoding a message to find why it was declined. |
| `build_test_suite` | `network` | Generates a set of test messages for a network: approval, financial, decline, reversal and echo. |
## Example
A conversation with the server connected:
> **You:** Generate a Visa auth for \$10 and explain the response if the issuer declines it for insufficient funds.
>
> **Assistant** calls `generate_test_message`, then `create_response` with `response_code: "51"`, then `explain_message`:
>
> MTI 0110: authorization response from acquirer. Card network: VISA. Card: 411111\*\*\*\*\*\*1111. Transaction type: Purchase. Amount: 10.00. Response code 51: Insufficient funds. Terminal: TERM0001. Merchant: MERCHANT123456.
## Using the server from Python
The server is a regular [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) server, so you can also call it in-process, for example in tests:
```python theme={null}
import asyncio
from mcp.client import Client
from iso8583sim.mcp import create_server
async def main():
async with Client(create_server()) as client:
result = await client.call_tool("generate_test_message", {"network": "VISA"})
message = result.structured_content["message"]
explained = await client.call_tool("explain_message", {"message": message})
print(explained.structured_content["summary"])
asyncio.run(main())
```
## Privacy
The server runs locally and makes no network calls. Messages you give the assistant are sent to your AI provider as part of the conversation, like any other text you paste, so use test data rather than real card numbers. `explain_message` masks the PAN in its summary.
# Networking
Source: https://iso8583sim.com/docs/net/index
iso8583sim can talk to real hosts over TCP, and can stand in for one.
Use it to test acquirer, switch or terminal software without a card network connection.
* **`iso8583sim serve`** runs a mock issuer host that answers by rules.
* **`iso8583sim send`** sends one message and shows the response.
* **`iso8583sim load`** sends many requests and reports throughput and latency.
* **`ISO8583Client`** and **`MockHost`** do the same from Python (asyncio).
Messages travel in a [wire format](../core/wire) (ASCII, BCD, EBCDIC...), each preceded by a length header and, optionally, a TPDU.
## Quick start
In one terminal, start a mock host:
```bash theme={null}
iso8583sim serve --port 8583
```
In another, generate a message and send it:
```bash theme={null}
iso8583sim generate --type auth -o msg.txt
iso8583sim send "$(cat msg.txt)" --port 8583
```
Or put it under load:
```bash theme={null}
iso8583sim load --port 8583 --count 10000 --concurrency 50
```
## Framing options
All three commands take the same options, and both ends must agree on them:
| Option | Values | Default |
| - | - | - |
| `--format` | `ascii-hex`, `ascii-binary`, `bcd`, `ebcdic` | `ascii-binary` |
| `--header` | `2b`, `4b` (binary length), `2a`, `4a` (ASCII digits) | `2b` |
| `--tpdu` | 10 hex digits, e.g. `6000010000` | none |
The length in the header covers everything after it, including the TPDU. On responses, the mock host swaps the TPDU's destination and source addresses.
## Mock host rules
Without a rules file, the mock host approves everything. A rules file lists rules in order; the first rule that matches a request decides the response.
```yaml theme={null}
# rules.yaml
rules:
- match: {amount_gt: 100000} # over 1,000.00
respond: "51" # insufficient funds
- match: {pan_prefix: ["4000", "5100"]}
respond: "05" # do not honor
delay_ms: 500 # answer slowly
- match: {pan_prefix: "4999"}
action: drop # never answer: tests your timeout handling
- match: {mti: "0800"}
respond: "00"
- respond: "00" # everything else is approved
```
```bash theme={null}
iso8583sim serve --port 8583 --rules rules.yaml --format bcd --tpdu 6000010000
```
Match conditions (all must hold):
| Key | Matches when |
| - | - |
| `mti` | The request MTI is one of these |
| `pan_prefix` | The PAN starts with one of these |
| `amount_gt`, `amount_lt` | Field 4 (minor units) is above / below this |
| `network` | The card network (detected from the PAN) is this |
| `fields` | These fields have exactly these values, e.g. `{"49": "978"}` |
Actions: `respond` (default, with `respond` as the field 39 code), `drop` (no answer) or `close` (close the connection). `delay_ms` delays any action.
Responses echo the request's matching fields (PAN, processing code, amount, times, STAN, IDs, currency, field 70), set field 39, and add an approval code in field 38 for approved authorizations and financial requests. Rules files can be JSON or YAML (YAML needs `pip install pyyaml`).
Press Ctrl+C (or send SIGTERM) to stop; the host prints a summary of what it received and how it answered.
## Python API
```python theme={null}
import asyncio
from iso8583sim.core.samples import sample_message
from iso8583sim.net import Framing, ISO8583Client, MockHost, Rule
from iso8583sim.wire import WireFormat
async def main():
fmt = WireFormat.bcd()
framing = Framing(header="2b", tpdu=bytes.fromhex("6000010000"))
host = MockHost(rules=[Rule(amount_gt=100000, respond="51"), Rule()], wire_format=fmt, framing=framing)
await host.start("127.0.0.1", 0) # port 0 picks a free port
async with ISO8583Client("127.0.0.1", host.port, wire_format=fmt, framing=framing, timeout=5) as client:
response = await client.send(sample_message(amount_minor_units=250000))
print(response.mti, response.fields[39]) # 0110 51
await host.stop()
asyncio.run(main())
```
The client matches responses to requests by STAN (field 11), so many requests can be in flight on one connection and responses may arrive in any order. Every request needs a STAN, and two requests waiting at the same time can't share one.
| Situation | What happens |
| - | - |
| No response in time | `TimeoutError` |
| Connection closes while waiting | `ConnectionError`; the next `send` reconnects |
| A response arrives after its timeout | Logged and ignored |
| TLS | Pass `ssl=True` or an `ssl.SSLContext` to the client |
`run_load` runs a load test from Python and returns a `LoadReport` with counts, response codes and latency percentiles.
## From an AI assistant
The [MCP server](../mcp-server) has a `send_to_host` tool. To stop an assistant from being used to reach arbitrary machines, it only connects to hosts listed in the `ISO8583SIM_ALLOWED_HOSTS` environment variable of the MCP server. The default is this machine only (`127.0.0.1,localhost,::1`). Entries can be a host (any port) or `host:port`:
```json theme={null}
{
"mcpServers": {
"iso8583sim": {
"command": "iso8583sim",
"args": ["mcp"],
"env": {"ISO8583SIM_ALLOWED_HOSTS": "127.0.0.1,uat-switch.internal:9000"}
}
}
}
```
## Performance
On an Apple M4 Pro, the client and mock host in one process handle about 12,000 round trips per second; with the host in a separate process, a load run reached about 20,000. See `benchmarks/BASELINE.md` and `benchmarks/bench_network.py`.
# Card Networks
Source: https://iso8583sim.com/docs/networks/index
iso8583sim supports multiple card networks with network-specific field definitions and validation rules.
## Supported Networks
| Network | PAN Prefixes | Documentation |
| - | - | - |
| VISA | 4xxx | [VISA Guide](./visa) |
| Mastercard | 51-55, 2221-2720 | [Mastercard Guide](./mastercard) |
| AMEX | 34, 37 | [Other Networks](./others) |
| Discover | 6011, 644-649, 65 | [Other Networks](./others) |
| JCB | 3528-3589 | [Other Networks](./others) |
| UnionPay | 62 | [Other Networks](./others) |
## Network Detection
Networks are automatically detected from the PAN (Primary Account Number):
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
parser = ISO8583Parser()
message = parser.parse(raw_message)
print(message.network) # CardNetwork.VISA
```
### Detection Rules
```python theme={null}
from iso8583sim.core.types import detect_network
detect_network("4111111111111111") # CardNetwork.VISA
detect_network("5500000000000004") # CardNetwork.MASTERCARD
detect_network("378282246310005") # CardNetwork.AMEX
detect_network("6011111111111117") # CardNetwork.DISCOVER
detect_network("3530111333300000") # CardNetwork.JCB
detect_network("6200000000000005") # CardNetwork.UNIONPAY
```
## Manual Network Specification
Override auto-detection by specifying the network:
```python theme={null}
from iso8583sim.core.types import CardNetwork
# During parsing
message = parser.parse(raw_message, network=CardNetwork.MASTERCARD)
# In message object
message = ISO8583Message(
mti="0100",
network=CardNetwork.VISA,
fields={...}
)
```
## Network-Specific Fields
Each network has specific field format requirements:
```python theme={null}
from iso8583sim.core.types import NETWORK_SPECIFIC_FIELDS
# Get VISA-specific field 62 definition
visa_field_62 = NETWORK_SPECIFIC_FIELDS[CardNetwork.VISA].get(62)
```
## Required Fields by Network
Different networks require different fields for authorization:
| Network | Required Fields |
| - | - |
| VISA | 2, 3, 4, 11, 14, 22, 24, 25 |
| Mastercard | 2, 3, 4, 11, 22, 24, 25 |
| AMEX | 2, 3, 4, 11, 22, 25 |
| Discover | 2, 3, 4, 11, 22 |
| JCB | 2, 3, 4, 11, 22, 25 |
| UnionPay | 2, 3, 4, 11, 22, 25, 49 |
## Network Validation
The validator checks network-specific requirements:
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import CardNetwork
validator = ISO8583Validator()
message.network = CardNetwork.VISA
errors = validator.validate_message(message)
# Checks:
# - Required fields are present
# - Network-specific field formats
# - Network-specific business rules
```
## Common Field Differences
### Field 22 - POS Entry Mode
Three digits: the first two are the **PAN entry mode** (how the card number was captured), the third is the terminal's **PIN entry capability**. For example, `051` is a chip read at a terminal that accepts PINs, and `812` is e-commerce with no PIN entry.
| PAN entry mode | Meaning |
| - | - |
| 00 | Unknown |
| 01 | Manual entry |
| 02 | Magnetic stripe |
| 03 | Barcode |
| 04 | OCR |
| 05 | Chip (ICC) |
| 06 | Contactless, mapping service applied |
| 07 | Contactless chip |
| 09 | E-commerce with DSRP cryptogram |
| 10 | Credential on file |
| 51 | Chip plus PIN at ATM |
| 71 | Contactless chip plus PIN at ATM |
| 79 | Chip fallback, hybrid terminal failure |
| 80 | Chip fallback to magnetic stripe |
| 81 | E-commerce |
| 82 | Auto entry via server |
| 90 | Magnetic stripe, full track read |
| 91 | Contactless magnetic stripe |
| 95 | Chip with unreliable CVV (Visa only) |
| PIN capability | Meaning |
| - | - |
| 0 | Unknown |
| 1 | Terminal can accept PINs |
| 2 | Terminal cannot accept PINs |
| 3 | Software-based PIN entry (mPOS) |
| 8 | PIN pad not working |
For VISA and Mastercard messages the validator checks field 22 against these codes, and reports `95` on a Mastercard message as a Visa-only code. Other networks use their own field 22 schemes, so their values aren't checked.
Source: the DE022 code reference aligned to the Mastercard Customer Interface Specification and Visa VisaNet Authorization-Only Online Messages ([link](https://docs.tech.sofi.com/pro/reference/api-reference-de022-codes)).
### Field 39 - Response Codes
| Code | VISA | Mastercard |
| - | - | - |
| 00 | Approved | Approved |
| 05 | Do Not Honor | Do Not Honor |
| 51 | Insufficient Funds | Insufficient Funds |
| 14 | Invalid Card | Invalid Card Number |
### Field 55 - EMV Data
EMV (chip card) data format is consistent across networks, but specific tags may vary in interpretation.
## Next Steps
* [VISA Specifics](./visa)
* [Mastercard Specifics](./mastercard)
* [Other Networks](./others)
# Mastercard
Source: https://iso8583sim.com/docs/networks/mastercard
Mastercard-specific field definitions and requirements for iso8583sim.
## Network Detection
Mastercard cards are detected by PAN prefix:
* 51-55 (traditional)
* 2221-2720 (2-series BINs)
* 16 digits
```python theme={null}
from iso8583sim.core.types import detect_network, CardNetwork
network = detect_network("5500000000000004")
assert network == CardNetwork.MASTERCARD
network = detect_network("2221000000000009")
assert network == CardNetwork.MASTERCARD
```
## Required Fields
Mastercard authorization requests require:
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (16 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 22 | POS Entry Mode | n3 |
| 24 | NII | n3 |
| 25 | POS Condition Code | n2 |
Note: Field 14 (Expiration) is optional but recommended.
## Mastercard-Specific Fields
### Field 48 - Additional Data
Mastercard's field 48 starts with a one-character **transaction category code** (for example `R` for retail), followed by any number of subelements. Each subelement is a 2-digit ID, a 2-digit length, then that many characters:
```python theme={null}
message.fields[48] = "R0103ABC" # TCC "R", then subelement 01 with 3 characters "ABC"
```
The validator checks this layout and reports truncated or misaligned subelements.
### Field 61 - POS Data Extended
Extended point-of-service data:
```python theme={null}
# Format varies by transaction type
message.fields[61] = "0000000001"
```
### DE127 - Private Data
Mastercard's Data Element 127 contains subfields for network data.
## POS Entry Modes (Field 22)
Common Mastercard values:
| Value | Meaning |
| - | - |
| 011 | Manual entry, terminal accepts PINs |
| 051 | Chip, terminal accepts PINs |
| 071 | Contactless M/Chip, terminal accepts PINs |
| 801 | Chip fallback to magnetic stripe, terminal accepts PINs |
| 812 | E-commerce, no PIN entry |
| 911 | Contactless magnetic stripe, terminal accepts PINs |
The third digit is PIN entry capability (`1` accepts PINs, `2` doesn't, `8` PIN pad not working). `95x` is a VISA-only code and is rejected for Mastercard. See [Field 22](.#field-22-pos-entry-mode) for every code the validator accepts.
## Response Codes (Field 39)
| Code | Description |
| - | - |
| 00 | Approved |
| 01 | Refer to Issuer |
| 04 | Pick Up Card |
| 05 | Do Not Honor |
| 12 | Invalid Transaction |
| 14 | Invalid Card Number |
| 41 | Lost Card |
| 43 | Stolen Card |
| 51 | Insufficient Funds |
| 54 | Expired Card |
| 55 | Incorrect PIN |
| 57 | Transaction Not Permitted |
| 58 | Transaction Not Permitted to Terminal |
| 61 | Exceeds Amount Limit |
| 65 | Exceeds Frequency Limit |
| 75 | PIN Tries Exceeded |
| 91 | Issuer System Error |
## Example: Mastercard Authorization Request
```python theme={null}
from iso8583sim.core.types import ISO8583Message, CardNetwork
from iso8583sim.core.builder import ISO8583Builder
message = ISO8583Message(
mti="0100",
network=CardNetwork.MASTERCARD,
fields={
0: "0100",
2: "5500000000000004", # Mastercard PAN
3: "000000", # Purchase
4: "000000010000", # $100.00
7: "1225120000", # Dec 25, 12:00:00
11: "123456", # STAN
14: "2512", # Exp Dec 2025
22: "051", # Chip, CVV reliable
24: "100", # NII
25: "00", # Normal transaction
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
builder = ISO8583Builder()
raw = builder.build(message)
```
## Example: Mastercard EMV Authorization
```python theme={null}
from iso8583sim.demo import generate_emv_auth
# Use a Mastercard PAN
emv_message = generate_emv_auth(
pan="5500000000000004",
amount=25000,
cryptogram="ABCDEF1234567890",
)
print(f"Network: {emv_message.network}") # CardNetwork.MASTERCARD
```
## MasterCard Payment Gateway (MPG)
Mastercard authorizations route through MasterCard Payment Gateway:
* Authorization timeouts: 30 seconds standard
* Reversal required for timeout scenarios
* Advice messages for offline approvals
## SecureCode / 3DS
For e-commerce transactions with 3D Secure:
```python theme={null}
# Field 48 contains authentication data
# Field 22 = 812 for authenticated e-commerce
message.fields[22] = "812"
```
## Validation
Mastercard-specific validation:
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import CardNetwork
validator = ISO8583Validator()
message.network = CardNetwork.MASTERCARD
errors = validator.validate_message(message)
# Validates:
# - All Mastercard required fields present
# - PAN passes Luhn check
# - PAN prefix matches Mastercard ranges
# - Field formats match Mastercard specs
```
## 2-Series BINs
Mastercard 2-series BINs (2221-2720) are supported:
```python theme={null}
# Both are valid Mastercard PANs
"5500000000000004" # Traditional
"2221000000000009" # 2-series
```
# Other Networks
Source: https://iso8583sim.com/docs/networks/others
Support for American Express, Discover, JCB, and UnionPay.
## American Express (AMEX)
### Detection
* PAN prefix: 34, 37
* 15 digits
```python theme={null}
from iso8583sim.core.types import detect_network, CardNetwork
network = detect_network("378282246310005")
assert network == CardNetwork.AMEX
```
### Required Fields
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (15 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 22 | POS Entry Mode | n3 |
| 25 | POS Condition Code | n2 |
### Example
```python theme={null}
from iso8583sim.core.types import ISO8583Message, CardNetwork
message = ISO8583Message(
mti="0100",
network=CardNetwork.AMEX,
fields={
0: "0100",
2: "378282246310005", # AMEX PAN (15 digits)
3: "000000",
4: "000000010000",
11: "123456",
22: "051",
25: "00",
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
```
### AMEX-Specific Notes
* Uses 15-digit PANs (not 16)
* Different response code meanings for some codes
* CVV location differs (4 digits on front)
***
## Discover
### Detection
* PAN prefix: 6011, 644-649, 65
* 16-19 digits
```python theme={null}
network = detect_network("6011111111111117")
assert network == CardNetwork.DISCOVER
```
### Required Fields
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (16-19 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 22 | POS Entry Mode | n3 |
### Example
```python theme={null}
message = ISO8583Message(
mti="0100",
network=CardNetwork.DISCOVER,
fields={
0: "0100",
2: "6011111111111117", # Discover PAN
3: "000000",
4: "000000010000",
11: "123456",
22: "051",
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
```
### Discover-Specific Notes
* D-PAS for tokenization
* Similar authorization flow to VISA
* ProtectBuy for 3D Secure
***
## JCB
### Detection
* PAN prefix: 3528-3589
* 16-19 digits
```python theme={null}
network = detect_network("3530111333300000")
assert network == CardNetwork.JCB
```
### Required Fields
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (16-19 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 22 | POS Entry Mode | n3 |
| 25 | POS Condition Code | n2 |
### Example
```python theme={null}
message = ISO8583Message(
mti="0100",
network=CardNetwork.JCB,
fields={
0: "0100",
2: "3530111333300000", # JCB PAN
3: "000000",
4: "000000010000",
11: "123456",
22: "051",
25: "00",
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
```
### JCB-Specific Notes
* Japan-based network with global acceptance
* J/Secure for 3D Secure authentication
* Currency conversion considerations for international transactions
***
## UnionPay
### Detection
* PAN prefix: 62
* 16-19 digits
```python theme={null}
network = detect_network("6200000000000005")
assert network == CardNetwork.UNIONPAY
```
### Required Fields
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (16-19 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 22 | POS Entry Mode | n3 |
| 25 | POS Condition Code | n2 |
| 49 | Currency Code | n3 |
Note: UnionPay requires Field 49 (Currency Code).
### Example
```python theme={null}
message = ISO8583Message(
mti="0100",
network=CardNetwork.UNIONPAY,
fields={
0: "0100",
2: "6200000000000005", # UnionPay PAN
3: "000000",
4: "000000010000",
11: "123456",
22: "051",
25: "00",
41: "TERM0001",
42: "MERCHANT123456 ",
49: "156", # CNY (Chinese Yuan)
}
)
```
### UnionPay-Specific Notes
* China-based network
* Field 49 required for currency identification
* QuickPass for contactless payments
* Supports both debit and credit products
***
## Response Codes Comparison
| Code | AMEX | Discover | JCB | UnionPay |
| - | - | - | - | - |
| 00 | Approved | Approved | Approved | Approved |
| 05 | Do Not Honor | Do Not Honor | Do Not Honor | Do Not Honor |
| 14 | Invalid Card | Invalid Card | Invalid Card | Invalid Card |
| 51 | Insufficient Funds | Insufficient Funds | Insufficient Funds | Insufficient Funds |
| 54 | Expired Card | Expired Card | Expired Card | Expired Card |
## Adding Custom Network Support
To add a new network:
```python theme={null}
# 1. Add to CardNetwork enum
class CardNetwork(Enum):
NEW_NETWORK = "NEW_NETWORK"
# 2. Add detection rule
def detect_network(pan: str) -> CardNetwork | None:
if pan.startswith("999"): # Example prefix
return CardNetwork.NEW_NETWORK
# ... existing rules
# 3. Add field definitions
NETWORK_SPECIFIC_FIELDS[CardNetwork.NEW_NETWORK] = {
62: FieldDefinition(...),
}
# 4. Add required fields
NETWORK_REQUIRED_FIELDS[CardNetwork.NEW_NETWORK] = [2, 3, 4, 11]
```
# VISA
Source: https://iso8583sim.com/docs/networks/visa
VISA-specific field definitions and requirements for iso8583sim.
## Network Detection
VISA cards are detected by PAN prefix:
* Starts with `4`
* 13, 16, or 19 digits
```python theme={null}
from iso8583sim.core.types import detect_network, CardNetwork
network = detect_network("4111111111111111")
assert network == CardNetwork.VISA
```
## Required Fields
VISA authorization requests require:
| Field | Name | Format |
| - | - | - |
| 2 | PAN | LLVAR (13-19 digits) |
| 3 | Processing Code | n6 |
| 4 | Amount | n12 |
| 11 | STAN | n6 |
| 14 | Expiration Date | n4 (YYMM) |
| 22 | POS Entry Mode | n3 |
| 24 | NII | n3 |
| 25 | POS Condition Code | n2 |
## VISA-Specific Fields
### Field 62 - VISA Private Data
Reserved for VISA-specific data elements:
```python theme={null}
message.fields[62] = "01" + "0123456789" # Example format
```
### Field 63 - VISA Additional Data
Additional transaction data:
| Sub-element | Description |
| - | - |
| 01 | Network ID |
| 02 | Settlement Basis |
| 03 | Transaction ID |
## POS Entry Modes (Field 22)
Common VISA values:
| Value | Meaning |
| - | - |
| 011 | Manual entry, terminal accepts PINs |
| 012 | Manual entry, no PIN entry |
| 051 | Chip, terminal accepts PINs |
| 071 | Contactless chip, terminal accepts PINs |
| 901 | Magnetic stripe (full track), terminal accepts PINs |
| 951 | Chip with unreliable CVV (VISA only), terminal accepts PINs |
The third digit is PIN entry capability: `1` means the terminal can accept PINs, `2` means it can't. See [Field 22](.#field-22-pos-entry-mode) for every code the validator accepts.
## Response Codes (Field 39)
| Code | Description |
| - | - |
| 00 | Approved |
| 01 | Refer to Issuer |
| 05 | Do Not Honor |
| 12 | Invalid Transaction |
| 14 | Invalid Card Number |
| 41 | Lost Card - Pick Up |
| 43 | Stolen Card - Pick Up |
| 51 | Insufficient Funds |
| 54 | Expired Card |
| 55 | Incorrect PIN |
| 61 | Exceeds Withdrawal Limit |
| 65 | Activity Count Limit Exceeded |
| 75 | PIN Tries Exceeded |
| 91 | Issuer Unavailable |
## Example: VISA Authorization Request
```python theme={null}
from iso8583sim.core.types import ISO8583Message, CardNetwork
from iso8583sim.core.builder import ISO8583Builder
message = ISO8583Message(
mti="0100",
network=CardNetwork.VISA,
fields={
0: "0100",
2: "4111111111111111", # VISA PAN
3: "000000", # Purchase
4: "000000010000", # $100.00
7: "1225120000", # Dec 25, 12:00:00
11: "123456", # STAN
14: "2512", # Exp Dec 2025
22: "051", # Chip, no PIN
24: "100", # NII
25: "00", # Normal transaction
41: "TERM0001",
42: "MERCHANT123456 ",
}
)
builder = ISO8583Builder()
raw = builder.build(message)
```
## Example: VISA EMV Authorization
```python theme={null}
from iso8583sim.demo import generate_emv_auth
emv_message = generate_emv_auth(
pan="4111111111111111",
amount=25000,
cryptogram="1234567890ABCDEF",
)
# Field 55 contains EMV data
print(emv_message.fields.get(55))
```
## VISA Base I/II
VISA uses two processing environments:
* **Base I**: Authorization and clearing
* **Base II**: Settlement and reconciliation
iso8583sim focuses on Base I authorization messages.
## Validation
VISA-specific validation:
```python theme={null}
from iso8583sim.core.validator import ISO8583Validator
from iso8583sim.core.types import CardNetwork
validator = ISO8583Validator()
message.network = CardNetwork.VISA
errors = validator.validate_message(message)
# Validates:
# - All VISA required fields present
# - PAN passes Luhn check
# - Field formats match VISA specs
```
# Performance Optimization Guide
Source: https://iso8583sim.com/docs/performance
This document describes the performance optimizations implemented in iso8583sim v0.3.0 and how to leverage them for high-throughput message processing.
## Overview
The iso8583sim library has been optimized for high-performance ISO 8583 message processing. Through a combination of pure Python optimizations and optional Cython compilation, the library achieves:
* **182,000+ TPS** for message parsing
* **150,000+ TPS** for message building
* **63,000+ TPS** for full roundtrip (build → parse → validate)
## Benchmark Results
### Performance Comparison
| Operation | v0.2.0 Baseline | v0.3.0 Optimized | Improvement |
| - | - | - | - |
| Parse (basic messages) | 95,000 TPS | 182,000 TPS | +91% |
| Parse (VISA messages) | 64,000 TPS | 121,000 TPS | +89% |
| Parse (EMV messages) | 104,000 TPS | 197,000 TPS | +89% |
| Build messages | 143,000 TPS | 150,000 TPS | +5% |
| Roundtrip (with validation) | 45,000 TPS | 63,000 TPS | +40% |
| Roundtrip (parse only) | 54,000 TPS | 78,000 TPS | +44% |
| Request/Response flow | 25,000 TPS | 32,000 TPS | +28% |
*Benchmarks run on macOS 15.7.2 arm64, Python 3.12.6, Apple Silicon*
### Running Benchmarks
```bash theme={null}
# Run all benchmarks
python benchmarks/bench_parser.py
python benchmarks/bench_builder.py
python benchmarks/bench_roundtrip.py
```
## Optimization Techniques
### Phase 1: Pure Python Optimizations
These optimizations are always active and require no additional dependencies.
#### 1. Lazy Logging
Log statements use `%s` formatting instead of f-strings to defer string formatting until the log level is enabled:
```python theme={null}
# Before (always formats string)
self.logger.debug(f"Parsed field {field_number}: {value}")
# After (only formats if DEBUG enabled)
self.logger.debug("Parsed field %d: %s", field_number, value)
```
#### 2. Dataclass Slots
All dataclasses use `slots=True` for reduced memory footprint and faster attribute access:
```python theme={null}
@dataclass(slots=True)
class ISO8583Message:
mti: str
fields: dict[int, str]
# ...
```
**Note:** This requires Python 3.10+.
#### 3. Dictionary Lookup Caching
Network and version-specific field definitions are cached at parse time to avoid repeated dictionary lookups:
```python theme={null}
class ISO8583Parser:
def __init__(self, version=ISO8583Version.V1987):
# Cache version fields at init (doesn't change)
self._version_fields = VERSION_SPECIFIC_FIELDS.get(version, {})
def parse(self, message, network=None):
# Cache network fields for this parse operation
self._network_fields = NETWORK_SPECIFIC_FIELDS.get(network, {})
```
#### 4. LRU Cache for Field Definitions
The `get_field_definition()` function uses `@lru_cache` for fast repeated lookups:
```python theme={null}
@lru_cache(maxsize=512)
def get_field_definition(field_number, network=None, version=ISO8583Version.V1987):
# Returns cached result for repeated calls
...
```
### Phase 2: Cython Compilation
For maximum performance, iso8583sim includes optional Cython extensions that compile hot paths to C code.
#### Getting the Extensions
The wheels on PyPI for Linux, macOS and Windows already include them. From a source checkout, an editable install compiles them (needs a C compiler):
```bash theme={null}
pip install -e .
python -c "import iso8583sim.core.parser as p; print(p._USE_CYTHON)" # True when the extensions are loaded
```
The following modules are compiled:
| Module | Functions | Speedup |
| - | - | - |
| `_bitmap.pyx` | `get_present_fields_fast()`, `build_bitmap_fast()` | 2-3x |
| `_parser_fast.pyx` | `parse_mti_fast()`, `parse_bitmap_fast()` | 1.5-2x |
| `_validator_fast.pyx` | `validate_pan_luhn()`, `is_valid_hex()`, etc. | 1.5-2x |
#### Automatic Fallback
The library automatically detects Cython availability and falls back to pure Python:
```python theme={null}
# In parser.py
try:
from ._bitmap import get_present_fields_fast
_USE_CYTHON = True
except ImportError:
_USE_CYTHON = False
def _get_present_fields(self, bitmap):
if _USE_CYTHON:
return get_present_fields_fast(bitmap)
# Pure Python fallback
...
```
### Phase 3: Object Pooling
For high-throughput applications processing millions of messages, object pooling reduces allocation overhead:
```python theme={null}
from iso8583sim.core.pool import MessagePool
from iso8583sim.core.parser import ISO8583Parser
# Create a pool
pool = MessagePool(size=100)
# Create parser with pool
parser = ISO8583Parser(pool=pool)
# Parse messages (pool reuses objects)
for raw_message in messages:
msg = parser.parse(raw_message)
# Process message...
# Return to pool when done
pool.release(msg)
```
#### Pool API
```python theme={null}
class MessagePool:
def __init__(self, size: int = 100):
"""Create pool with maximum size."""
def acquire(self, mti, fields, ...) -> ISO8583Message:
"""Get message from pool or create new one."""
def release(self, msg: ISO8583Message) -> None:
"""Return message to pool for reuse."""
def clear(self) -> None:
"""Clear all pooled messages."""
@property
def size(self) -> int:
"""Current number of pooled messages."""
```
#### When to Use Pooling
Object pooling is most beneficial when:
* Processing millions of messages in a long-running application
* Memory fragmentation is a concern
* You can explicitly control message lifecycle
For typical usage, the `slots=True` dataclasses are already efficient enough that pooling provides minimal benefit.
## Configuration Recommendations
### High-Throughput Production
For maximum performance in production:
1. **Make sure the Cython extensions are loaded:**
```bash theme={null}
python -c "import iso8583sim.core.parser as p; print(p._USE_CYTHON)"
```
2. **Use object pooling for sustained loads:**
```python theme={null}
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
```
3. **Reuse parser/builder instances** (they cache field definitions):
```python theme={null}
# Good - reuse parser
parser = ISO8583Parser()
for msg in messages:
result = parser.parse(msg)
# Bad - creates new parser each time
for msg in messages:
result = ISO8583Parser().parse(msg)
```
4. **Disable debug logging in production:**
```python theme={null}
import logging
logging.getLogger('iso8583sim').setLevel(logging.WARNING)
```
### Development/Testing
For development, the pure Python implementation is sufficient:
```python theme={null}
from iso8583sim.core.parser import ISO8583Parser
from iso8583sim.core.builder import ISO8583Builder
parser = ISO8583Parser()
builder = ISO8583Builder()
```
## System Requirements
| Feature | Minimum Python |
| - | - |
| Core library | 3.10+ |
| `slots=True` dataclasses | 3.10+ |
| Cython extensions | 3.10+ |
| Object pooling | 3.10+ |
## Profiling Your Application
To identify bottlenecks in your specific use case:
```python theme={null}
import cProfile
import pstats
# Profile your code
cProfile.run('your_message_processing_function()', 'output.prof')
# Analyze results
stats = pstats.Stats('output.prof')
stats.sort_stats('cumulative')
stats.print_stats(20)
```
## Future Optimization Opportunities
Potential areas for further optimization:
1. **memoryview** - Zero-copy parsing for very large messages
2. **Async batch processing** - Parallel processing of message batches
3. **SIMD operations** - Vectorized bitmap operations for modern CPUs
4. **Pre-compiled message templates** - For common message patterns
# REST API
Source: https://iso8583sim.com/docs/rest/index
Run iso8583sim as an HTTP service and try every endpoint from this page.
The REST API exposes parsing, building, validation, explanation, generation and version conversion over HTTP. The playground on each endpoint page sends requests to the public demo at `https://api.iso8583sim.com`, so you can try it without installing anything. The demo has LLM features turned off and is rate limited. Only send test card numbers.
To use LLM features, or to run it against your own systems, start it locally and switch the playground's server to `http://127.0.0.1:8000`:
```bash theme={null}
pip install "iso8583sim[web]"
iso8583sim web # http://127.0.0.1:8000, interactive docs at /docs
```
The API has no authentication. It listens on `127.0.0.1` by default. Don't expose it on a public network unless you run it in [public mode](../web#public-mode).
See [REST API guide](../web) for running it in Docker, error codes and examples.
# Troubleshooting
Source: https://iso8583sim.com/docs/troubleshooting
Common issues and solutions when using iso8583sim.
## Installation Issues
### "Module not found: iso8583sim"
**Problem:** Python cannot find the iso8583sim module.
**Solutions:**
1. Verify installation:
```bash theme={null}
pip show iso8583sim
```
2. Check you're in the right virtual environment:
```bash theme={null}
which python
pip list | grep iso8583sim
```
3. Reinstall:
```bash theme={null}
pip uninstall iso8583sim
pip install iso8583sim
```
### Cython extensions are not loaded
**Problem:** `python -c "import iso8583sim.core.parser as p; print(p._USE_CYTHON)"` prints `False`.
The PyPI wheels for Linux, macOS and Windows include the compiled extensions. On other platforms pip installs the pure-Python wheel (iso8583sim still works, only slower).
**Solutions:**
1. Upgrade pip so it can pick a compiled wheel: `pip install --upgrade pip`, then reinstall iso8583sim.
2. To build from source, install a C compiler and reinstall with `pip install --no-binary iso8583sim iso8583sim`:
* **macOS:** `xcode-select --install`
* **Linux:** `apt install build-essential`
* **Windows:** Install Visual Studio Build Tools
3. The library works without Cython - it falls back to pure Python automatically.
### LLM provider not available
**Problem:** `ProviderNotAvailableError: anthropic provider package is not installed`
**Solutions:**
1. Install the provider:
```bash theme={null}
pip install iso8583sim[anthropic]
# or
pip install anthropic>=0.40.0
```
2. Check what's installed:
```python theme={null}
from iso8583sim.llm import list_installed_providers
print(list_installed_providers())
```
### LLM provider not configured
**Problem:** `ProviderConfigError: Anthropic API key not found`
**Solutions:**
1. Set the environment variable:
```bash theme={null}
export ANTHROPIC_API_KEY="sk-ant-..."
```
2. Pass API key directly:
```python theme={null}
from iso8583sim.llm.providers.anthropic import AnthropicProvider
provider = AnthropicProvider(api_key="sk-ant-...")
```
3. Check what's configured:
```python theme={null}
from iso8583sim.llm import list_available_providers
print(list_available_providers())
```
## Parsing Issues
### ParseError: Invalid bitmap
**Problem:** `ParseError: Invalid bitmap format`
**Causes:**
* Bitmap is not exactly 16 hex characters
* Contains non-hex characters
* Message is truncated
**Solution:**
```python theme={null}
# Bitmap must be exactly 16 hex characters (8 bytes)
# Example valid bitmap: "7024058020C09000"
# Check your message structure:
message = "0100702406C120E09000..."
# ^^^^ = MTI (4 chars)
# ^^^^^^^^^^^^^^^^ = Bitmap (16 chars)
# ^^ = Field data starts here
```
### ParseError: Field length exceeds maximum
**Problem:** A field value is longer than allowed.
**Solution:**
```python theme={null}
# Check field definition
from iso8583sim.core.types import get_field_definition
field_def = get_field_definition(2)
print(f"Field 2 max length: {field_def.max_length}")
# For LLVAR fields, check the length prefix
# "164111111111111111" = 16 chars, value is 16 chars
# "204111111111111111" = 20 chars, but value is only 18 chars - MISMATCH
```
### ParseError: Unknown field
**Problem:** Parsing fails on a network-specific field.
**Solution:**
```python theme={null}
# Specify the network when parsing
from iso8583sim.core.types import CardNetwork
parser = ISO8583Parser()
message = parser.parse(raw, network=CardNetwork.VISA)
```
## Building Issues
### BuildError: Validation failed
**Problem:** Message doesn't pass validation during build.
**Solution:**
```python theme={null}
# Check what's wrong before building
from iso8583sim.core.validator import ISO8583Validator
validator = ISO8583Validator()
errors = validator.validate_message(message)
for error in errors:
print(error)
# Fix the issues, then build
```
### BuildError: Unknown field definition
**Problem:** Building fails because field isn't recognized.
**Solution:**
```python theme={null}
# Check field exists
from iso8583sim.core.types import ISO8583_FIELDS
if field_number not in ISO8583_FIELDS:
print(f"Field {field_number} is not a standard ISO 8583 field")
# For network-specific fields, set the network
message.network = CardNetwork.VISA
```
### Field padding incorrect
**Problem:** Field values aren't padded correctly.
**Solution:**
```python theme={null}
# The builder handles padding automatically
# But you can check field definitions:
from iso8583sim.core.types import get_field_definition
field_def = get_field_definition(4) # Amount
print(f"Type: {field_def.field_type}")
print(f"Length: {field_def.max_length}")
print(f"Padding: {field_def.padding_char} ({field_def.padding_direction})")
# Field 4 (Amount): 12 digits, left-padded with zeros
# "1000" becomes "000000001000"
```
## Validation Issues
### PAN fails Luhn check
**Problem:** `Field 2 (PAN) failed Luhn check`
**Solution:**
```python theme={null}
# Use a valid test PAN
# These are valid Luhn-check PANs for testing:
VALID_TEST_PANS = {
"VISA": "4111111111111111",
"Mastercard": "5500000000000004",
"AMEX": "378282246310005",
"Discover": "6011111111111117",
}
```
### Missing required field
**Problem:** `Missing required field: 22`
**Solution:**
```python theme={null}
# Different networks require different fields
# Check requirements for your network:
from iso8583sim.core.validator import ISO8583Validator
validator = ISO8583Validator()
required = validator.network_required_fields.get(CardNetwork.VISA, [])
print(f"VISA requires: {required}")
# Add missing fields to your message
message.fields[22] = "051" # POS Entry Mode
```
## LLM Issues
### LLMError: API rate limit
**Problem:** `LLMError: Rate limit exceeded`
**Solution:**
```python theme={null}
import time
# Add delay between requests
for message in messages:
try:
explanation = explainer.explain(message)
except LLMError:
time.sleep(1) # Wait and retry
explanation = explainer.explain(message)
```
### Generated message invalid
**Problem:** LLM generates an invalid message.
**Solution:**
```python theme={null}
# Always validate generated messages
message = generator.generate(description, validate=False)
# Then validate manually
errors = validator.validate_message(message)
if errors:
print("LLM generated invalid message:")
for error in errors:
print(f" - {error}")
```
## Performance Issues
### Slow parsing
**Problem:** Parsing is slower than expected.
**Solutions:**
1. Check the Cython extensions are loaded (see [Cython extensions are not loaded](#cython-extensions-are-not-loaded)).
2. Reuse parser instances:
```python theme={null}
# Good
parser = ISO8583Parser()
for msg in messages:
result = parser.parse(msg)
# Bad
for msg in messages:
result = ISO8583Parser().parse(msg)
```
3. Disable debug logging:
```python theme={null}
import logging
logging.getLogger('iso8583sim').setLevel(logging.WARNING)
```
### Memory usage high
**Problem:** Memory grows with message volume.
**Solution:**
```python theme={null}
# Use object pooling
from iso8583sim.core.pool import MessagePool
pool = MessagePool(size=1000)
parser = ISO8583Parser(pool=pool)
for raw in messages:
msg = parser.parse(raw)
process(msg)
pool.release(msg) # Return to pool
```
## Getting Help
If you can't resolve an issue:
1. Check the [API Reference](./api/core) for correct usage
2. Search [GitHub Issues](https://github.com/bassrehab/ISO8583-Simulator/issues)
3. Open a new issue with:
* Python version (`python --version`)
* iso8583sim version (`pip show iso8583sim`)
* Minimal code to reproduce
* Full error traceback
# REST API
Source: https://iso8583sim.com/docs/web/index
iso8583sim includes a REST API built with FastAPI, for tools and services that aren't written in Python.
**For test environments.** The API has no authentication. It listens on `127.0.0.1` by default. Don't expose it on a public network, except in public mode (below).
## Public demo
A public demo runs at `https://api.iso8583sim.com`, and the [REST API reference](/docs/rest) playground calls it by default. Try it without installing anything:
```bash theme={null}
curl -s https://api.iso8583sim.com/generate -H "Content-Type: application/json" -d '{"network": "VISA"}'
```
It runs in public mode, so LLM features are off and requests are rate limited. Only send test card numbers. Switch the playground's server to `http://127.0.0.1:8000` to use your own instance.
## Running
```bash theme={null}
pip install iso8583sim[web]
iso8583sim web
```
The server starts on `http://127.0.0.1:8000`. Options: `--host`, `--port` and `--reload` (restart on code changes).
You can also run it with uvicorn directly:
```bash theme={null}
uvicorn iso8583sim.web.app:app --port 8000
```
Interactive API docs are served at [`/docs`](http://127.0.0.1:8000/docs) and the OpenAPI schema at [`/openapi.json`](http://127.0.0.1:8000/openapi.json).
## Endpoints
| Method | Path | Purpose |
| - | - | - |
| GET | `/health` | Liveness check, returns the package version |
| POST | `/parse` | Parse a raw message into its MTI, bitmap and named fields (EMV decoded when present) |
| POST | `/build` | Build a raw message from an MTI and field values |
| POST | `/validate` | Validate a message, and optionally check it against other networks' rules |
| POST | `/explain` | Plain English summary, rule-based by default or from an LLM with `"llm": true` |
| POST | `/generate` | Generate a valid test message from options, or from a description with an LLM |
| POST | `/convert` | Convert a message to another ISO 8583 version |
Common request fields:
* `message`: raw message string (MTI, hex bitmap, field data)
* `version`: `"1987"` (default), `"1993"` or `"2003"`
* `network`: `VISA`, `MASTERCARD`, `AMEX`, `DISCOVER`, `JCB` or `UNIONPAY`. Detected from the PAN when omitted.
## Examples
Generate a test message and explain it:
```bash theme={null}
curl -s -X POST localhost:8000/generate \
-H 'content-type: application/json' \
-d '{"network": "MASTERCARD", "amount_minor_units": 2500}'
```
```json theme={null}
{
"message": "0100722405800...",
"mti": "0100",
"network": "MASTERCARD",
"fields": [{"number": 2, "name": "Primary Account Number (PAN)", "value": "5555555555554444"}, "..."],
"source": "template"
}
```
```bash theme={null}
curl -s -X POST localhost:8000/explain \
-H 'content-type: application/json' \
-d '{"message": "0100722405800..."}'
```
```json theme={null}
{
"summary": "MTI 0100: authorization request from acquirer. Card network: MASTERCARD. Card: 555555******4444. Transaction type: Purchase. Amount: 25.00 USD. ...",
"source": "rules",
"mti": "0100",
"fields": ["..."]
}
```
Build a message. Field numbers are JSON object keys, so they are strings:
```bash theme={null}
curl -s -X POST localhost:8000/build \
-H 'content-type: application/json' \
-d '{"mti": "0800", "fields": {"7": "1215143022", "11": "000001", "70": "301"}}'
```
Check a message against every network (an empty `against` list means all):
```bash theme={null}
curl -s -X POST localhost:8000/validate \
-H 'content-type: application/json' \
-d '{"message": "0100...", "against": []}'
```
## Errors
| Status | When |
| - | - |
| 422 | Invalid request, or a message that can't be parsed, built or converted. `detail` explains why. |
| 403 | LLM features requested on a server in public mode |
| 502 | The LLM failed or returned an unusable message (`/explain` and `/generate` with an LLM) |
| 503 | No LLM provider is configured on the server |
`/validate` is the exception: a message that fails to parse is returned as `{"valid": false, "errors": ["Parse error: ..."]}` with status 200.
## Docker
The repository includes a `Dockerfile` that runs the API with the security extra installed:
```bash theme={null}
docker build -t iso8583sim .
docker run --rm -p 8000:8000 iso8583sim
```
Inside the container the server listens on all interfaces so the port mapping works. `-p 127.0.0.1:8000:8000` keeps it reachable from your machine only.
To use LLM features, pass the provider's API key:
```bash theme={null}
docker run --rm -p 8000:8000 -e ANTHROPIC_API_KEY iso8583sim
```
The image installs the `anthropic` extra only. Build with `--build-arg EXTRAS=web,security,llm` for all providers.
## Public mode
Set `ISO8583SIM_PUBLIC=1` to run the API as a public demo:
```bash theme={null}
docker run --rm -p 8000:8000 -e ISO8583SIM_PUBLIC=1 iso8583sim
```
In public mode:
* LLM features return 403, even when an API key is set, so nobody can spend your key.
* Browsers on any site can call the API (CORS allows every origin). There are no cookies or credentials, so this allows nothing that `curl` can't already do.
Every server rejects oversized requests: messages over 32,768 characters, field values over 1,998 characters and descriptions over 2,000 characters. Put rate limiting in front of a public server, for example at a reverse proxy.