name: x9-validate
description: Validate JSON payloads against the X9.150 OpenAPI schema
user-invocable: true
tools:
- Bash
- Read
- Glob
context: fork
inject:
- spec/openapi.yaml
- !find spec/ -name "*.yaml" -maxdepth 1
X9.150 Payload Validator
You validate JSON payloads against the X9.150 OpenAPI schema (spec/openapi.yaml). You auto-detect the schema type and report constraint violations with explanations.
Instructions
Accept input — the user provides either:
- A file path to a JSON file (template or generated payload)
- Inline JSON pasted in the message
- A template name (e.g., "01_coffee_shop") to validate from
templates/
Auto-detect the schema type based on the payload structure:
- Has
creditor + bill + paymentMethods → PaymentRequest
- Has
payment + id (32-char hex) at root → NotificationPayload
- Has only
qrCodeContent → FetchRequestPayload
- Has only
statusCode → SignedStatusCodePayload
- Has
creditor + bill but no id/status → Template (pre-generation format, validate subset)
Run validation using the project's own validation infrastructure:
python3.13 -c "
import json, yaml, sys
from jsonschema import Draft7Validator
import referencing
from referencing.jsonschema import DRAFT7
with open('spec/openapi.yaml') as f:
spec = yaml.safe_load(f)
payload = json.loads('''<PAYLOAD_JSON>''')
spec_uri = 'http://x9.150/openapi.yaml'
resource = referencing.Resource.from_contents(spec, default_specification=DRAFT7)
registry = referencing.Registry().with_resource(uri=spec_uri, resource=resource)
schema = {'\$ref': f'{spec_uri}#/components/schemas/<SCHEMA_NAME>'}
validator = Draft7Validator(schema, registry=registry)
errors = list(validator.iter_errors(payload))
if errors:
for e in errors:
path = '.'.join(str(p) for p in e.absolute_path) or '(root)'
print(f'FAIL: {path} — {e.message}')
sys.exit(1)
else:
print('PASS: payload is valid')
"
- Report results with:
- Detected schema type
- PASS/FAIL status
- For each violation: field path, constraint violated, expected vs actual, explanation
- Suggestions for fixing violations
Common Validation Rules to Check
Timestamps
- Must match
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$
- Must include milliseconds (
.000)
- Must end with
Z (UTC)
UUIDs
id: 32 hex chars, no dashes (^[0-9a-fA-F]{32}$)
correlationId: standard UUID with dashes
Monetary Amounts
- Must be integers (minor units, e.g., cents)
amount: 56 = $0.56, not $56.00
- Adjustments use
SignedMonetaryAmount (can be negative for discounts)
Bill Rules
paymentTiming: "deferred" requires invoice.dueDate
tip.allowed: true requires tip.range with min and max
PaymentMethod
validUntil is required on each payment method
networks must have at least one entry
- Traditional networks require
routingNumber (9 digits), accountNumber (1-17 digits), protectionType
Creditor
MCC must be exactly 4 digits
address.country must be 2 uppercase letters (ISO 3166-1)
- US postal codes:
^\d{5}(-\d{4})?$
Template vs Full PaymentRequest
Templates (in templates/) are partial — they lack runtime fields like id, revision, createdAt, sentAt, validUntil, status, qrCodeContent, paymentNotification. These are added by qr_generator.py. When validating a template, skip required-field checks for these runtime fields and focus on structural/format validation of the fields that ARE present.
Error Explanation Style
For each error, explain:
- What the constraint is (with the regex or rule)
- What value was found
- How to fix it
- Why the spec requires this (brief context)
1---2name: x9-validate3description: You validate JSON payloads against the X9.150 OpenAPI schema (spec/openapi.yaml). You auto-detect the schema type and report constraint violations with explanations.4---5
6---
7name: x9-validate
8description: Validate JSON payloads against the X9.150 OpenAPI schema
9user-invocable: true
10tools:
11 - Bash
12 - Read
13 - Glob
14context: fork
15inject:
16 - spec/openapi.yaml
17 - !find spec/ -name "*.yaml" -maxdepth 1
18---
19
20# X9.150 Payload Validator
21
22You validate JSON payloads against the X9.150 OpenAPI schema (`spec/openapi.yaml`). You auto-detect the schema type and report constraint violations with explanations.
23
24## Instructions
25
261. **Accept input** — the user provides either:
27 - A file path to a JSON file (template or generated payload)
28 - Inline JSON pasted in the message
29 - A template name (e.g., "01_coffee_shop") to validate from `templates/`
30
312. **Auto-detect the schema type** based on the payload structure:
32 - Has `creditor` + `bill` + `paymentMethods` → **PaymentRequest**
33 - Has `payment` + `id` (32-char hex) at root → **NotificationPayload**
34 - Has only `qrCodeContent` → **FetchRequestPayload**
35 - Has only `statusCode` → **SignedStatusCodePayload**
36 - Has `creditor` + `bill` but no `id`/`status` → **Template** (pre-generation format, validate subset)
37
383. **Run validation** using the project's own validation infrastructure:
39
40```bash
41python3.13 -c "
42import json, yaml, sys
43from jsonschema import Draft7Validator
44import referencing
45from referencing.jsonschema import DRAFT7
46
47with open('spec/openapi.yaml') as f:
48 spec = yaml.safe_load(f)
49
50payload = json.loads('''<PAYLOAD_JSON>''')
51
52spec_uri = 'http://x9.150/openapi.yaml'
53resource = referencing.Resource.from_contents(spec, default_specification=DRAFT7)
54registry = referencing.Registry().with_resource(uri=spec_uri, resource=resource)
55schema = {'\$ref': f'{spec_uri}#/components/schemas/<SCHEMA_NAME>'}
56
57validator = Draft7Validator(schema, registry=registry)
58errors = list(validator.iter_errors(payload))
59if errors:
60 for e in errors:
61 path = '.'.join(str(p) for p in e.absolute_path) or '(root)'
62 print(f'FAIL: {path} — {e.message}')
63 sys.exit(1)
64else:
65 print('PASS: payload is valid')
66"
67```
68
694. **Report results** with:
70 - Detected schema type
71 - PASS/FAIL status
72 - For each violation: field path, constraint violated, expected vs actual, explanation
73 - Suggestions for fixing violations
74
75## Common Validation Rules to Check
76
77### Timestamps
78- Must match `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$`
79- Must include milliseconds (`.000`)
80- Must end with `Z` (UTC)
81
82### UUIDs
83- `id`: 32 hex chars, no dashes (`^[0-9a-fA-F]{32}$`)
84- `correlationId`: standard UUID with dashes
85
86### Monetary Amounts
87- Must be integers (minor units, e.g., cents)
88- `amount: 56` = $0.56, not $56.00
89- Adjustments use `SignedMonetaryAmount` (can be negative for discounts)
90
91### Bill Rules
92- `paymentTiming: "deferred"` requires `invoice.dueDate`
93- `tip.allowed: true` requires `tip.range` with `min` and `max`
94
95### PaymentMethod
96- `validUntil` is required on each payment method
97- `networks` must have at least one entry
98- Traditional networks require `routingNumber` (9 digits), `accountNumber` (1-17 digits), `protectionType`
99
100### Creditor
101- `MCC` must be exactly 4 digits
102- `address.country` must be 2 uppercase letters (ISO 3166-1)
103- US postal codes: `^\d{5}(-\d{4})?$`
104
105## Template vs Full PaymentRequest
106
107Templates (in `templates/`) are partial — they lack runtime fields like `id`, `revision`, `createdAt`, `sentAt`, `validUntil`, `status`, `qrCodeContent`, `paymentNotification`. These are added by `qr_generator.py`. When validating a template, skip required-field checks for these runtime fields and focus on structural/format validation of the fields that ARE present.
108
109## Error Explanation Style
110
111For each error, explain:
1121. What the constraint is (with the regex or rule)
1132. What value was found
1143. How to fix it
1154. Why the spec requires this (brief context)