Important
You are browsing documentation for version 6.1 of OroCommerce, supported until 2029. Read the documentation for the latest LTS version to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
Microservice HTTP Contract
The bundle communicates with the AI Smart Order microservice over HTTP with JSON bodies. The microservice is operated by Oro and is not part of the oro/ai-smart-order package. This page describes the two requests the bundle sends, the response it consumes, and the ways to adjust the request or the response on the bundle side.
Recognition Request
Oro\Bundle\EntityRecognitionBundle\Processor\EntityRecognitionProcessor builds an Oro\Bundle\OrderRecognitionBundle\Request\Model\DTO\RecognizeOrderRequestDTO from the input string returned by the source providers and the decrypted API key, and passes it to Oro\Bundle\OrderRecognitionBundle\Connector\OrderRecognitionConnector. The connector wraps the DTO into Oro\Bundle\OrderRecognitionBundle\Request\RecognizeOrderRequest and sends it through Oro\Bundle\OrderRecognitionBundle\Client\OrderRecognitionApiClient.
Method and path —
POST {order_recognition_url}/recognize. The base URL is theoro_order_recognition.order_recognition_urlsystem configuration option; a trailing slash is removed before the path is appended.Headers —
Content-Type: application/jsonandAccept: application/json. The recognition request sends no authentication header; the API key is part of the body.Body — A JSON object with two keys:
{ "base64_blob_file": "JVBERi0xLjQK...", "api_key": "<api key>" }
base64_blob_fileis the base64-encoded content of the document. The defaultOro\Bundle\EntityRecognitionBundle\Source\SymfonyFileSourceProviderproduces it from the temporary file created byFileProcessor.Timeouts — The client uses the
oro_order_recognition.http_clientservice, a Symfony HTTP client created withtimeout: 600andmax_duration: 600, so a large document can take up to ten minutes. A missing base URL raises a\RuntimeExceptionin the client; a missing API key raises a\RuntimeExceptioninEntityRecognitionProcessor.
Health Check
Oro\Bundle\OrderRecognitionBundle\Checker\SmartOrderConnectionChecker verifies the connection through Oro\Bundle\OrderRecognitionBundle\Connector\CheckServiceHealthConnector:
Method and path —
GET {order_recognition_url}/health. The request always uses the savedorder_recognition_urland API key, so the Check Connection button on the system configuration page tests the saved settings; save the changed values before checking them.Headers —
X-Api-Key: <api key>in addition to the JSON content headers. The key is read throughOro\Bundle\OrderRecognitionBundle\Manager\AuthenticationManager; when no key is configured,SmartOrderConnectionCheckerdoes not send the request and reports a failure.Response — A
200status with a JSON object.SmartOrderConnectionChecker::check()returns the decoded JSON body, or an empty array when the request fails or returns a non-200status.checkConnection(), the method ofOro\Bundle\OrderRecognitionBundle\Checker\ConnectionCheckerInterface, returnstruewhenstatusisokorwarning. The Check Connection button callscheck()and expects thestatuskey with theokvalue (ConnectionCheckerInterface::STATUS_OK); it reports a failure for any other result and includes the optionalmessagekey only whenstatusiswarning(STATUS_WARNING).
The health check uses the oro_order_recognition.client.api_client.standard_timeout client service with the default Symfony HTTP client timeouts, not the long-timeout client of the recognition request.
Response Envelope
A successful recognition returns the 200 status and a JSON object. EntityRecognitionProcessor reads two keys:
data.recognized_data— The recognized order data described below. It is stored in theoutputcontext key.data.image_base64_blob_file— Optional. A base64-encoded image of the first page of the document.PreviewFileProcessorstores it as a second attachment that the draft order edit page shows as the preview.
{
"data": {
"recognized_data": {
"order": {},
"customer": {},
"customerUser": {},
"lineItems": [],
"shippingAddress": {},
"billingAddress": {},
"other": {}
},
"image_base64_blob_file": "iVBORw0KGgo..."
}
}
Any other status is an error. The connector decodes the body, logs it to the ai_smart_order channel, maps the error.code key to a user-facing message, and throws Oro\Bundle\OrderRecognitionBundle\Exception\InvalidResponseException with that message. See Error Codes.
Recognized Data Reference
The following sample shows every key that the bundle reads. All keys except lineItems are optional: a missing optional key leaves the corresponding order field empty and, where a state is recorded, marks the field as invalid. A missing or empty lineItems array rejects the recognition. A line item without a numeric quantity keeps an empty quantity, and a line item without a productUnitCode keeps an empty unit marked as invalid.
Important
The JSON contract can be adjusted for a specific document layout through a support request, for example, to capture line item notes or custom line item and order attributes from purchase orders. The bundle ignores a key it does not know. For this reason, a new key still needs a processor that maps it. See Map Additional Payload Fields.
{
"order": {
"poNumber": "PO-2026-0001",
"currency": "USD",
"shipUntil": "2026-10-15",
"paymentTerm": "Net 30",
"shippingMethod": "Flat Rate"
},
"customer": {
"name": "Sample Customer",
"subsidiary": "Sample Customer North"
},
"customerUser": {
"firstName": "Alex",
"lastName": "Sample",
"email": "alex.sample@example.com"
},
"lineItems": [
{
"lineNumber": 1,
"pageNumber": 1,
"manufacturerCode": "MFR-100",
"productSku": "SKU-100",
"skuCandidates": ["SKU100", "SKU-100-A"],
"productName": "Sample Product",
"quantity": 10,
"productUnitCode": "item",
"value": "12.50",
"currency": "USD"
}
],
"shippingAddress": {
"firstName": "Alex",
"middleName": "",
"lastName": "Sample",
"street": "1 Sample Street",
"street2": "Suite 100",
"city": "Sample City",
"regionCode": "CA",
"regionName": "California",
"postalCode": "90000",
"countryCode": "US",
"countryName": "United States",
"phone": "+1 555 0100"
},
"billingAddress": {},
"other": {
"deliveryInstructions": "Call before delivery"
}
}
Key |
Consumer |
Behavior |
|---|---|---|
|
|
Copied to |
|
|
Upper-cased and validated as an ISO currency code. An invalid or missing code falls back to the |
|
|
Parsed with |
|
|
Matched case-insensitively against the payment term label. A miss marks the payment term state as invalid. |
|
|
Matched case-insensitively against the names of the configured shipping methods; the first type of the matched method is used. A miss is logged only. |
|
|
Looked up by exact name (subsidiary first), then through the customer search adapter. A subsidiary name is also matched against the child customers of the found customer. |
|
|
Looked up by email, then by both names, then through the search adapter. The found customer user must belong to the recognized customer. Skipped when the |
|
|
One |
|
|
Looked up by SKU (manufacturer code, then product SKU, then each candidate), then by the default product name, then through the product search adapter. A miss, or a match that is not a simple product, stores the code as a free-form product SKU and name and marks the product state as invalid. The non-empty codes are also stored as |
|
|
Matched against the unit precisions of the found product. A product with a single unit precision uses that unit regardless of the recognized code. |
|
|
Cast to float. |
|
|
Parsed as a localized decimal into the line item price. The price state is valid only when the value equals the product price found by the pricing provider. |
|
|
Upper-cased and validated as an ISO currency code. An empty or invalid code falls back to the order currency. |
|
|
Stored as |
|
|
Looked up by ISO2 code, then by name. |
|
|
Looked up by the combined |
|
|
When the country and region are found, an existing customer address with the same country, region, city, and street is looked up, then the customer address search adapter is tried. A hit copies the customer address into the order address; a miss creates a new order address from the recognized keys and marks the addresses state as invalid. |
|
|
Same keys and lookup as |
|
|
A free-form object. See The Other Object. |
The Other Object
other contains any value the microservice recognizes that has no dedicated key. OrderProcessor renders it into the order customer notes as one - <key>: <value> line per entry; a nested object or array produces a - <key>: line followed by its entries indented by four spaces. The sample above produces the customer notes - deliveryInstructions: Call before delivery.
Unknown Fields
A key that is not listed above is ignored by the processors, but the whole recognized_data object is stored verbatim in OrderRecognition::rawData by RecognitionDataProcessor. Custom code can therefore read an additional field either from the output context key during the recognition or from $order->getRecognition()->getRawData() afterwards. Both options are described in Map Additional Payload Fields.
Error Codes
When the microservice returns a non-200 status, Oro\Bundle\OrderRecognitionBundle\Connector\ErrorMessageHandler reads error.code from the decoded body and selects the translated message of InvalidResponseException:
Code |
Constant |
Message Key |
Meaning |
|---|---|---|---|
|
|
|
The microservice refused the input. The user is asked to check the file and try again. |
|
|
|
The microservice is temporarily unable to process a PDF file. The user is asked to try a JPG or PNG file. |
any other or missing |
|
|
A generic failure. The user is asked to reload the page and try again. |
The message is shown in the dashboard widget for a synchronous upload and is listed as the error cause in the failure email for an asynchronous one. See Error Handling for how the exception travels through the pipeline.
Changing the Request or Normalizing the Response
The bundle has no dedicated hook for the payload. Use one of the following extension points, ordered from the closest to the HTTP layer to the closest to the order:
Decorate the connector — Implement
Oro\Bundle\EntityRecognitionBundle\Connector\ConnectorInterfaceand decorateOro\Bundle\OrderRecognitionBundle\Connector\OrderRecognitionConnectorto rewrite the decoded response beforeEntityRecognitionProcessorreads it. This is the place to rename a vendor-specific key into a key the processors understand. See the example below.Decorate the API client — Implement
Oro\Bundle\OrderRecognitionBundle\Client\OrderRecognitionApiClientInterfaceand decorateOro\Bundle\OrderRecognitionBundle\Client\OrderRecognitionApiClientto change the PSR-7 request, for example, to add a header. The health check uses the separateoro_order_recognition.client.api_client.standard_timeoutservice, so the decoration affects only the recognition request.Add a source provider — Implement
Oro\Bundle\EntityRecognitionBundle\Source\SourceProviderInterfaceto change how thebase64_blob_filevalue is produced from the input. See Add a Source Provider.Add a processor — Register a processor with a priority between
250and210to rewrite theinputcontext key before the request, or between210and190to rewrite theoutputcontext key before any entity is built. See Add a Custom Processor.
The following decorator skeleton rewrites the decoded response, for example, to rename a vendor-specific purchase_order_number key into order.poNumber. The execute() method receives the request DTO, passes it to the decorated connector, and returns the array that EntityRecognitionProcessor reads:
namespace Acme\Bundle\SmartOrderBundle\Connector;
use Oro\Bundle\EntityRecognitionBundle\Connector\ConnectorInterface;
use Oro\Bundle\OrderRecognitionBundle\Request\Model\RequestDataInterface;
class NormalizingConnectorDecorator implements ConnectorInterface
{
public function __construct(
private readonly ConnectorInterface $inner,
) {
}
#[\Override]
public function execute(RequestDataInterface $requestBodyDTO): array
{
$response = $this->inner->execute($requestBodyDTO);
// normalize $response['data']['recognized_data']
return $response;
}
}
services:
Acme\Bundle\SmartOrderBundle\Connector\NormalizingConnectorDecorator:
decorates: Oro\Bundle\OrderRecognitionBundle\Connector\OrderRecognitionConnector
arguments:
- '@.inner'
Note
EntityRecognitionProcessor reads data.recognized_data from the returned array without a fallback. A decorator must keep the envelope and return an array that still contains this key.