Troubleshooting API Platform

Edit on GitHub

This document provides solutions to common issues when working with API Platform in Spryker.

Generation issues

Resources not generating

Symptom: Running docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate completes but no resources are created.

Possible causes:

  1. Schema file location is incorrect

    ❌ src/Pyz/Glue/Customer/api/customers.resource.yml
    ✅ src/Pyz/Glue/Customer/resources/api/backend/customers.resource.yml
    
  2. API type not configured

    Check config/{APPLICATION}/packages/spryker_api_platform.php:

    return static function (SprykerApiPlatformConfig $sprykerApiPlatform): void {
        $sprykerApiPlatform->apiTypes([
            'backend', // Must match directory name
        ]);
    };
    
  3. Bundle not registered

    Verify config/{APPLICATION}/bundles.php includes:

    SprykerApiPlatformBundle::class => ['all' => true],
    

Solution:

# Debug to see what's being discovered
docker/sdk cli glue  api:debug --list

# Check schema validation
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --validate-only

# Force regeneration
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --force

Schema validation errors

Symptom: Generation fails with schema validation errors.

Common errors:

# Error: Invalid operation type
❌ operations:
    - type: CREATE

✅ operations:
    - type: Post

# Error: Invalid property typetype: int
✅ type: integer

# Error: Missing resource name
❌ resource:
    shortName: customers

✅ resource:
    name: Customers
    shortName: customers

# Error: Property declares both "items" and "openapiContext.items"
# openapiContext is merged on top of the derived schema, so the hand-written
# shape would win and the typed element schema would be discarded silently.
❌ categories:
    type: array
    items:
        type: object
        properties:
            categoryKey: { type: string }
    openapiContext:
        items:
            type: object
            properties:
                categoryKey: { type: string }

✅ categories:
    type: array
    items:
        type: object
        properties:
            categoryKey: { type: string }

# Error: Property is both a relationship and an inline object list
# A relationship property already receives a docblock describing its target
# resource, and only one docblock is emitted per property.
❌ includes:
    - relationshipName: addresses
      targetResource: CustomersAddresses
  properties:
    addresses:
        type: array
        items:
            type: object
            properties:
                city: { type: string }

✅ includes:
    - relationshipName: addresses
      targetResource: CustomersAddresses

Solution:

  1. Check schema against examples in documentation

  2. Use --validate-only flag for detailed validation:

    docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --validate-only
    
  3. Inspect merged schema:

    docker/sdk cli glue  api:debug resource-name --show-merged
    

A list property still publishes as an untyped array

Symptom: A type: array property has an items block, but the published contract shows "type": "array" with no items reference, and SDK generators produce an untyped collection.

Possible causes:

  1. items is nested under openapiContext instead of being a sibling of type: array

    Only a sibling triggers typing. An items block under openapiContext is documentation passthrough: it generates no class and produces no reference.

    ❌ categories:
        type: array
        openapiContext:
            items:
                type: object
                properties:
                    categoryKey: { type: string }
    
    ✅ categories:
        type: array
        items:
            type: object
            properties:
                categoryKey: { type: string }
    
  2. items.type is a scalar

    A list of scalars generates no element class and no reference. This is expected — there is nothing to type.

  3. The resource was not regenerated after the schema change

    docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate
    
  4. Stale generated code is still being served

    See Inspecting generated code to confirm what is on disk.

Solution:

Confirm the generated resource class carries a @var array<\Generated\…> docblock on the property. That docblock is what API Platform reads to build the element reference — if it is absent, the contract falls back to an untyped array. See Typed collections in the published contract.

Runtime issues

Provider/Processor not found

Symptom:

Error: Class "Pyz\Glue\Customer\Api\Backend\Provider\CustomerBackendProvider" not found

Possible causes:

  1. Class doesn’t exist or namespace is wrong
  2. Not registered in the Dependency Injection container
  3. Typo in the schema file

Solution:

  1. Verify the class exists and namespace matches:

    namespace Pyz\Glue\Customer\Api\Backend\Provider;
    
    class CustomerBackendProvider implements ProviderInterface
    
  2. Ensure services are auto-discovered in ApplicationServices.php:

    $services->load('Pyz\\Glue\\', '../../../src/Pyz/Glue/');
    
  3. Check class name in the resource schema file of the module matches exactly:

    provider: "Pyz\\Glue\\Customer\\Api\\Backend\\Provider\\CustomerBackendProvider"
    

Validation not working

Symptom: API accepts invalid data despite validation rules.

Possible causes:

  1. Validation schema file not found
  2. Wrong operation name in validation schema
  3. Validation groups not matching

Solution:

  1. Ensure validation file exists:

    ✅ resources/api/backend/customers.validation.yml
    
  2. Match operation names to HTTP methods:

    post:      # For POST /customers
      email:
        - NotBlank
    
    patch:     # For PATCH /customers/{id}
      email:
        - Optional:
            constraints:
              - Email
    
  3. Check generated resource class (for example Generated\Api\Storefront\CustomersStorefrontResource) has validation attributes:

    #[Assert\NotBlank(groups: ['customers:create'])]
    #[Assert\Email(groups: ['customers:create'])]
    public ?string $email = null;
    

API documentation UI not displaying correctly

Symptom: When accessing the root URL of your API application, you see:

  • Missing styles/CSS
  • Broken JavaScript functionality
  • Plain HTML without formatting
  • “Failed to load resource” errors in the browser console

Cause: Assets were not installed after API Platform integration.

Solution:

Run the appropriate assets:install command for your application:

For Glue application

docker/sdk cli glue assets:install public/Glue/assets  --symlink

For GlueStorefront

docker/sdk cli GLUE_APPLICATION=GLUE_STOREFRONT glue assets:install public/GlueStorefront/assets/  --symlink

For GlueBackend

docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue assets:install public/GlueBackend/assets/  --symlink

Then verify the documentation UI loads correctly by visiting the root URL:

  • Storefront: https://glue-storefront.mysprykershop.com/
  • Backend: https://glue-backend.mysprykershop.com/
Required after integration

The assets:install command must be run after integrating API Platform and whenever API Platform assets are updated. This is a required step documented in Integrate API Platform.

404 Not Found for API endpoints

Symptom: API requests return 404.

Possible causes:

  1. Router not configured
  2. Routes not loaded
  3. Wrong URL format

Solution:

  1. Verify SymfonyFrameworkRouterPlugin is registered:

    // RouterDependencyProvider
    protected function getRouterPlugins(): array
    {
        return [
            new GlueRouterPlugin(),
            new SymfonyFrameworkRouterPlugin(), // Must be present
        ];
    }
    
  2. Check API documentation for correct URLs:

    Storefront: https://glue-storefront.mysprykershop.com/
    Backend: https://glue-backend.mysprykershop.com/
    

    The interactive API documentation is available at the root URL of each application.

  3. Use correct URL format:

    ❌ /api/v1/customers
    ✅ /customers
    

Every request answers 404 with code 007 or a bare 500, and nothing is logged

Symptom: A resource that used to work suddenly answers 404 with {"errors":[{"message":"Not found","status":404,"code":"007"}]} or a plain 500 Internal Server Error, the response carries no exception details, and data/logs stays empty.

Cause:

The Glue application routes a request to the API Platform kernel only after its own router answered 404. If the kernel then throws before the router resolved an operation, for example in a kernel.request subscriber, an authenticator, or while instantiating one of their dependencies, no exception listener can build a response and the original Glue 404 is sent instead. An exception inside a provider or processor is caught by the API Platform stack and becomes a 500.

In both cases the exception is logged to the container’s standard error stream, not to a file. In debug mode the response also carries the exception class, message, file, line, and trace.

Solution:

  1. Read the log of the Glue container, for example:

    docker logs spryker_glue_backend_eu_1 --since 5m 2>&1 | grep -i "exception"
    
  2. Enable debug mode for local development. SPRYKER_DEBUG_ENABLED=1 switches the Glue, Glue Backend, and Glue Storefront kernels into debug mode: the Symfony container is rebuilt when code changes, and exception details are rendered in the response. See Enable debug mode for the Glue kernels.

  3. If the 404 persists in debug mode, the route really is unknown to the API Platform kernel. Regenerate the resources and clear the kernel cache:

    docker/sdk cli "GLUE_APPLICATION=GLUE_BACKEND vendor/bin/glue api:generate"
    rm -rf data/cache/GlueBackend/<environment>
    

Requests without an Accept header are rejected or return the wrong format

Symptom: A client request that omits the Accept header — or sends only Accept: */* — returns 406 Not Acceptable, or a response in a format other than the legacy application/vnd.api+json. The legacy Glue REST API silently accepted the same request and answered with application/vnd.api+json.

Cause: API Platform runs content negotiation that requires a satisfiable Accept header and does not assume the legacy Glue default. This is a behavioral difference from the legacy Glue REST stack.

Solution:

  1. Upgrade spryker/api-platform to 1.15.0 or higher. Its AcceptHeaderFallbackSubscriber restores the legacy behavior — a missing or */* Accept header defaults to application/vnd.api+json:

    composer update spryker/api-platform --with-dependencies
    
  2. If you cannot upgrade, send an explicit Accept header from the client:

    curl -H "Accept: application/vnd.api+json" https://glue-backend.mysprykershop.com/customers
    

Pagination not working

Symptom: All results returned instead of paginated response.

Solution:

  1. Enable pagination in the schema file of the defining module:

    resource:
      paginationEnabled: true
      paginationItemsPerPage: 10
    
  2. In the provider, read page[limit] and page[offset] with buildPaginationTransfer(), pass the transfer to the facade, and report the total number of results with setCollectionPagination():

    $paginationTransfer = $this->buildPaginationTransfer();
    $criteriaTransfer->setPagination($paginationTransfer);
    
    $collectionTransfer = $this->facade->getCollection($criteriaTransfer);
    
    $nbResults = $collectionTransfer->getPagination()?->getNbResults();
    if ($nbResults !== null) {
        $this->setCollectionPagination($paginationTransfer->getOffsetOrFail(), $paginationTransfer->getLimitOrFail(), $nbResults);
    }
    
  3. Use the JSON:API pagination query parameters:

    GET /customers?page[limit]=20&page[offset]=20
    

    A page number parameter such as page=2 is not supported. If meta.pagination is missing from the response, the facade did not return nbResults in the collection’s pagination transfer.

Client cannot change items per page

Symptom: The page[limit] query parameter is ignored.

Solution:

Enable client-side items-per-page control and set a maximum limit in the resource schema:

resource:
  paginationEnabled: true
  paginationItemsPerPage: 10
  paginationClientItemsPerPage: true
  paginationMaximumItemsPerPage: 100

Without paginationClientItemsPerPage: true, the itemsPerPage query parameter has no effect. The paginationMaximumItemsPerPage option prevents clients from requesting excessively large pages.

Client cannot disable pagination

Symptom: The pagination=false query parameter is ignored and results are still paginated.

Solution:

Enable client-side pagination control in the resource schema:

resource:
  paginationClientEnabled: true

Without paginationClientEnabled: true, the pagination query parameter has no effect.

For a full reference of all pagination options, see Resource schemas — Pagination.

Dependency Injection issues

Services not autowired

Symptom:

Cannot autowire service "CustomerBackendProvider": argument "$customerFacade"
references class "CustomerFacadeInterface" but no such service exists.

Solution:

  1. Register facade in the respective applications ApplicationServices.php:

    use Pyz\Zed\Customer\Business\CustomerFacadeInterface;
    use Pyz\Zed\Customer\Business\CustomerFacade;
    
    $services->set(CustomerFacadeInterface::class, CustomerFacade::class);
    
  2. Ensure constructor uses interface type hints:

    public function __construct(
        private CustomerFacadeInterface $customerFacade,  // ✅ Interface
    ) {}
    

Performance issues

Slow API responses

Symptom: API endpoints respond slowly.

Solution:

  1. Verify that Opcache is enabled (opcache.enable: 1). Without it, PHP recompiles the whole application on every request, which adds a flat overhead of seconds to every endpoint regardless of the amount of data. See Opcache activation.

  2. Enable Symfony cache:

    docker/sdk cli glue  cache:warmup
    
  3. Use pagination for collections

  4. Optimize database queries in Provider

  5. Use API Platform’s built-in caching features

Development tips

Debugging schema merging

See which schemas contribute to final resource:

docker/sdk cli glue  api:debug customers --api-type=backend --show-sources

Output:

Source Files (priority order):
  ✓ vendor/spryker/customer/resources/api/backend/customers.resource.yml (CORE)
  ✓ src/SprykerFeature/CRM/resources/api/backend/customers.resource.yml (FEATURE)
  ✓ src/Pyz/Glue/Customer/resources/api/backend/customers.resource.yml (PROJECT)

Inspecting generated code

View the generated resource class:

cat src/Generated/Api/Backend/CustomersBackendResource.php

Check for:

  • Correct property types
  • Validation attributes
  • API Platform metadata

Testing with dry-run

Preview generation without writing files:

docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --dry-run

Getting help

If you encounter issues not covered here:

  1. Check logs:

    tail -f var/log/application.log
    tail -f var/log/exception.log
    
  2. Enable debug mode:

    <?php
    
    // config/{APPLICATION}/packages/spryker_api_platform.php
    
    declare(strict_types = 1);
    
    use Symfony\Config\SprykerApiPlatformConfig;
    
    return static function (SprykerApiPlatformConfig $sprykerApiPlatform): void {
        $sprykerApiPlatform->debug(true);
    };
    
  3. Validate environment:

    php -v  # Check PHP version (8.3+)
    composer show | grep api-platform
    docker/sdk cli glue  debug:container | grep -i api
    
  4. Common error patterns:

Error Likely cause Solution
Class not found Autoloading issue Run composer dump-autoload
Service not found DI configuration Check ApplicationServices.php
Route not found Router not configured Add SymfonyFrameworkRouterPlugin
Validation failed Schema mismatch Regenerate with --force
Cache is stale Outdated cache Run cache:clear
API docs UI broken/unstyled Assets not installed Run docker/sdk cli glue assets:install