Overview
The OpenFX v1 API follows an additive-only evolution policy. New fields, new enum values, and new endpoints may be added to the API at any time without a major version change. Your integration must be built to handle these additions gracefully.
This page describes the contract between OpenFX and API consumers. If you follow these guidelines, your integration will continue to work correctly as the API evolves — no code changes required when new features are released.
The Additive-Only Policy
The following changes can happen without a version bump (i.e., while still under /v1/):
| Change | Example | Your Action |
|---|
| New field added to a response | A settlementDate field appears on the Payment resource. | Ignore it if you do not need it. |
| New enum value added | A new payment status on_hold appears alongside existing statuses. | Handle it as an unknown status (see below). |
| New endpoint added | POST /v1/payments/batch is introduced. | No impact on existing integrations. |
| New query parameter added | A currency filter is added to GET /v1/payments. | No impact — existing queries continue to work. |
| New webhook event type added | settlement_route.triggered events begin firing. | Ignored by your webhook handler unless you subscribe. |
| New error code added | A corridor_unavailable error code appears. | Your error handler should already handle unknown codes by type. |
The following changes are breaking and will only happen in a new major version (e.g., /v4/):
| Breaking Change | Example |
|---|
| Removing a field from responses | The fees field is removed from Payment. |
| Removing an enum value | The ach rail is removed from the PaymentRail enum. |
| Changing a field’s type | amount changes from string to number. |
| Renaming a field | sendAmount is renamed to sourceAmount. |
| Changing URL path structure | /payments moves to /payment-orders. |
| Removing an endpoint | DELETE /counterparties/{id} is removed. |
Client Requirements
1. Ignore Unknown Fields in Responses
Your JSON deserialization must not fail when it encounters fields that were not present when you built your integration.
# CORRECT: Use a permissive approach
import requests
response = requests.get(f"{BASE_URL}/payments/{payment_id}", headers=headers)
payment = response.json()
# Access only the fields you need — unknown fields are silently ignored
status = payment["status"]
amount = payment["sendAmount"]["amount"]
# If using dataclasses or Pydantic, configure them to ignore extras
from pydantic import BaseModel, ConfigDict
class Payment(BaseModel):
model_config = ConfigDict(extra="ignore") # Ignore unknown fields
id: str
status: str
sendAmount: dict
# New fields added by OpenFX will be silently ignored
Do not use strict schema validation on API responses. If your client validates response bodies against a fixed JSON Schema or rejects objects with unexpected properties, it will break when OpenFX adds new fields. Strict validation is appropriate for requests (see below), not for responses.
2. Handle Unknown Enum Values
When processing enum fields (like status, rail, or type), always include a default branch for values you do not recognize:
# CORRECT: Handle unknown enum values gracefully
def process_payment_status(payment: dict):
status = payment["status"]
match status:
case "created":
handle_created(payment)
case "processing":
handle_processing(payment)
case "completed":
handle_completed(payment)
case "failed":
handle_failed(payment)
case "canceled":
handle_canceled(payment)
case _:
# Unknown status — log it and handle gracefully
log.warning(f"Unknown payment status: {status}")
handle_unknown_status(payment)
A good default handler for unknown enum values is to log the value for investigation and treat the resource as “pending” or “in progress” until you deploy an update that handles the new value.
3. Request Bodies Enforce Strict Schemas
While responses are permissive (additional fields are allowed), request bodies enforce additionalProperties: false. This means the API will reject request bodies that contain fields it does not recognize.
This is a deliberate security measure to prevent mass assignment vulnerabilities — you cannot accidentally set internal fields by including extra properties in your request.
// This request will be REJECTED (unknown field "internalPriority"):
POST /v1/payments
{
"sourceAccountId": "acc_01953e1a5f4b7002",
"counterpartyId": "cpt_01953e1a5f4b7004",
"rail": "ach",
"sendAmount": { "currency": "USD", "value": "150000" },
"internalPriority": "high" // REJECTED — unknown field
}
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Request body contains unknown field: internalPriority.",
"status": 400,
"requestId": "req_01953e1a5f4b7b20",
"retryable": false
}
}
The asymmetry is intentional: responses are open (ignore unknown fields), requests are closed (reject unknown fields). This gives OpenFX freedom to add response fields while protecting against unintended request data.
URI Versioning
The API uses URI path versioning (/v1/). The version in the URL path is the only versioning mechanism — there is no Accept-Version header or date-based versioning.
https://sandbox.api.openfx.com/v1/payments
^^
URI version prefix
Breaking changes will only be introduced in a new major version (e.g., /v4/). When a new major version is released:
- The previous version will continue to operate for a published deprecation period.
- Migration guides will be provided.
- Both versions will run concurrently during the transition window.
Webhook Forward Compatibility
The same principles apply to webhook payloads:
- New event types may be added at any time. Your webhook handler should ignore event types it does not recognize.
- New fields may appear in event payloads. Your deserialization must not break on unknown fields.
- New enum values may appear in event data (e.g., a new payment status). Handle them with a default branch.
# CORRECT: Webhook handler that ignores unknown event types
def handle_webhook(event: dict):
event_type = event["type"]
handlers = {
"payment.created": handle_payment_created,
"payment.completed": handle_payment_completed,
"payment.failed": handle_payment_failed,
"inbound_payment.received": handle_inbound_received,
}
handler = handlers.get(event_type)
if handler:
handler(event)
else:
# Unknown event type — acknowledge it (return 200) but do not process
log.info(f"Ignoring unknown event type: {event_type}")
Always return a 200 response from your webhook endpoint, even for event types you do not handle. Returning a non-2xx response will cause OpenFX to retry the delivery, wasting resources on both sides.
Summary of Client Obligations
| Rule | Implementation |
|---|
| Ignore unknown response fields | Use permissive JSON parsing; do not validate responses against a strict schema. |
| Handle unknown enum values | Always include a default branch in switch statements and match expressions. |
| Do not send unknown request fields | Request schemas are strict — only include documented fields. |
| Handle new webhook event types | Return 200 for all events; ignore types you do not recognize. |
| Do not depend on field ordering | JSON field order in responses is not guaranteed and may change. |
| Do not depend on error message text | Use type and code for programmatic logic, not the message string. |
- Error Handling — how new error codes are introduced under existing error types
- Pagination — how new filter parameters may appear on list endpoints