--- name: oro-api-dev description: OroCommerce API development reference covering JSON:API configuration, entity exposure, processors, filters, actions, authentication, CORS, batch API, storefront API, and testing. Use when configuring API resources, creating custom API processors, exposing entities via REST, building API integrations, or testing API endpoints in OroCommerce. --- # OroCommerce API Developer Reference for API development on OroCommerce. Docs: https://doc.oroinc.com/backend/api/ ## Overview Oro API implements REST conforming to [JSON:API specification](http://jsonapi.org/). Built on `OroApiBundle` using ChainProcessor, EntitySerializer, and Symfony Forms. Base URL: `https://{host}/api/{resource}` Sandbox: `https://{host}/api/doc` By default, only custom entities, dictionaries, and enumerations are accessible. Other entities must be explicitly enabled. ## Enabling an Entity Create `Resources/config/oro/api.yml`: ```yaml api: entities: Acme\Bundle\ExampleBundle\Entity\Example: ~ ``` Then run: ```bash php bin/console oro:api:cache:clear php bin/console oro:api:doc:cache:clear # rebuild sandbox docs ``` ## Configuration Reference ```yaml api: entities: Acme\Bundle\ExampleBundle\Entity\Example: # Exclude entity entirely exclude: false # Field configuration fields: id: ~ name: description: "Human-readable name" secretField: exclude: true # hide from API renamedField: property_path: original_name # map API name to entity property computedField: data_type: string property_path: _ # custom processor handles this # Filter configuration filters: fields: name: ~ status: allow_array: true # ?filter[status][]=a&filter[status][]=b createdAt: exclude: false # Sorter configuration sorters: fields: name: ~ createdAt: ~ # Action configuration actions: get: true get_list: true create: true update: true delete: false # disable delete delete_list: false # disable bulk delete # Subresources subresources: relatedItems: actions: get_subresource: exclude: false ``` ## Actions Standard CRUD actions available per entity: | Action | HTTP | URL | Description | |---|---|---|---| | get | GET | /api/{resource}/{id} | Single resource | | get_list | GET | /api/{resource} | Collection | | create | POST | /api/{resource} | Create | | update | PATCH | /api/{resource}/{id} | Update | | delete | DELETE | /api/{resource}/{id} | Delete | | delete_list | DELETE | /api/{resource} | Bulk delete | | get_subresource | GET | /api/{resource}/{id}/{sub} | Subresource | | get_relationship | GET | /api/{resource}/{id}/relationships/{rel} | Relationship | | update_relationship | PATCH | /api/{resource}/{id}/relationships/{rel} | Set relationship | | add_relationship | POST | /api/{resource}/{id}/relationships/{rel} | Add to relationship | | delete_relationship | DELETE | /api/{resource}/{id}/relationships/{rel} | Remove from relationship | ## Adding Filters to Existing Oro Entities Expose custom fields on Oro core entities as API filters without modifying core code: ```yaml api: entities: Oro\Bundle\CustomerBundle\Entity\CustomerUser: filters: fields: erp_contact_id: data_type: integer property_path: erp_contact_id Oro\Bundle\CustomerBundle\Entity\Customer: filters: fields: erp_customer_id: data_type: integer property_path: erp_customer_id ``` This lets API consumers query: `GET /api/customerusers?filter[erp_contact_id]=12345` The `property_path` must match an extended field added via migration (`oro_options`). ## Custom Processors Processors modify request/response handling in the API pipeline. Each action has a chain of processors executed in order. ```php namespace Acme\Bundle\ExampleBundle\Api\Processor; use Oro\Bundle\ApiBundle\Processor\CustomizeLoadedData\CustomizeLoadedDataContext; use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; class ComputeExampleField implements ProcessorInterface { public function process(ContextInterface $context): void { /** @var CustomizeLoadedDataContext $context */ $data = $context->getData(); // modify $data $data['computedField'] = 'computed_value'; $context->setData($data); } } ``` Register with tag: ```yaml services: acme.api.processor.compute_example_field: class: Acme\Bundle\ExampleBundle\Api\Processor\ComputeExampleField tags: - name: oro.api.processor action: customize_loaded_data class: Acme\Bundle\ExampleBundle\Entity\Example ``` ### Processor Tag Attributes | Attribute | Purpose | Example Values | |---|---|---| | `action` | Which API action to hook | `get`, `get_list`, `create`, `update`, `delete`, `customize_loaded_data` | | `group` | Pipeline stage within the action | `initialize`, `normalize_input`, `security_check`, `transform_data`, `normalize_result` | | `priority` | Execution order (higher = earlier) | `-10` (after defaults), `250` (early) | | `class` | Entity class to scope processor to | `Oro\Bundle\CustomerBundle\Entity\CustomerUser` | ### Intercepting Create/Update with FormContext Use `FormContext` to modify request data during create/update (e.g., resolve a custom field to a relationship): ```php use Oro\Bundle\ApiBundle\Model\Error; use Oro\Bundle\ApiBundle\Processor\FormContext; use Oro\Bundle\ApiBundle\Request\Constraint; use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; class ResolveCustomerFromErpId implements ProcessorInterface { public function __construct(private ManagerRegistry $registry) {} public function process(ContextInterface|FormContext $context): void { $requestData = $context->getRequestData(); $erpId = $requestData['erp_customer_id'] ?? null; if ($erpId === null) { return; } $customer = $this->registry ->getRepository(Customer::class) ->findOneBy(['erp_customer_id' => $erpId]); if ($customer !== null) { $requestData['customer'] = [ 'class' => Customer::class, 'id' => $customer->getId() ]; $context->setRequestData($requestData); } } } ``` Register on the `create` action in `transform_data` group: ```yaml services: acme.api.processor.resolve_customer: class: Acme\Bundle\ExampleBundle\Api\Processor\ResolveCustomerFromErpId arguments: ['@doctrine'] tags: - { name: oro.api.processor, action: create, group: transform_data, priority: -40, class: Oro\Bundle\CustomerBundle\Entity\CustomerUser } ``` ## Authentication API supports OAuth 2.0: ```bash # Get token curl -X POST https://host/oauth2-token \ -d "grant_type=client_credentials" \ -d "client_id=" \ -d "client_secret=" # Use token curl -H "Authorization: Bearer " \ -H "Accept: application/vnd.api+json" \ https://host/api/examples ``` Configure OAuth apps in admin: System > User Management > OAuth Applications. ## Filters ``` GET /api/examples?filter[name]=test GET /api/examples?filter[status][]=active&filter[status][]=pending GET /api/examples?filter[createdAt]>2024-01-01 GET /api/examples?page[number]=2&page[size]=10 GET /api/examples?sort=-createdAt,name GET /api/examples?include=relatedEntity GET /api/examples?fields[examples]=name,status ``` ## Batch API For bulk operations: ``` POST /api/examples/batch Content-Type: application/vnd.api+json { "data": [ { "type": "examples", "attributes": { ... } }, ... ] } ``` Async batch API processes large datasets via MQ. Docs: https://doc.oroinc.com/api/batch-api/ ## Storefront API Separate API for storefront (customer-facing). Configure in `Resources/config/oro/api_frontend.yml`: ```yaml api: entities: Acme\Bundle\ExampleBundle\Entity\Example: # storefront-specific config ``` Different authentication (customer user tokens). Docs: https://doc.oroinc.com/backend/api/storefront/ ## CORS Configuration ```yaml # config/config.yml oro_api: cors: preflight_max_age: 600 allow_origins: - 'https://frontend.example.com' allow_headers: - 'Content-Type' - 'Authorization' expose_headers: - 'X-Include' allow_credentials: true ``` ## Testing API Oro provides base test cases for API functional tests: ```php use Oro\Bundle\ApiBundle\Tests\Functional\RestJsonApiTestCase; class ExampleApiTest extends RestJsonApiTestCase { public function testGetList(): void { $response = $this->cget(['entity' => 'examples']); $this->assertResponseContains('expected_response.yml', $response); } public function testCreate(): void { $response = $this->post(['entity' => 'examples'], 'create_request.yml'); $this->assertResponseContains('create_response.yml', $response); } } ``` Test fixtures in `Tests/Functional/Api/DataFixtures/`. Expected responses in `Tests/Functional/Api/responses/`. ## CLI Commands ```bash php bin/console oro:api:cache:clear # clear API config cache php bin/console oro:api:doc:cache:clear # rebuild sandbox docs php bin/console oro:api:config:dump-reference # dump config structure php bin/console oro:api:debug # list available API resources php bin/console oro:api:debug --sub-resources Example # show entity subresources ```