---
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=<id>" \
  -d "client_secret=<secret>"

# Use token
curl -H "Authorization: Bearer <token>" \
  -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
```
