Every integration built against the Shopware 6 API eventually has to deal with failure: a malformed request, an expired token, a validation rule rejecting a payload, or a downstream service timing out. How gracefully your application recovers from these situations often matters more than how well it performs on the happy path. Shopware 6.7 ships with a consistent, JSON:API-inspired error format across both the Store API and Admin API, which makes building resilient error handling significantly easier once you understand the conventions.

The Shopware Error Response Format

Rather than returning ad-hoc error strings, Shopware wraps every failed request in a structured errors array. Each entry follows a predictable shape:

{
  "errors": [
    {
      "status": "400",
      "code": "FRAMEWORK__WRITE_TYPE_INTOLERABLE_INDEX",
      "title": "Bad Request",
      "detail": "Expected an associative array as data for field 'translations'.",
      "meta": {
        "parameters": {
          "field": "translations"
        }
      },
      "source": {
        "pointer": "/0/translations"
      }
    }
  ]
}

The status field mirrors the HTTP status code, code is a stable machine-readable identifier you can safely branch on in application logic, detail is human-readable context for logs, and source.pointer tells you exactly which part of the payload triggered the failure. Relying on code instead of parsing detail strings is the difference between an integration that survives a Shopware update and one that silently breaks because a message changed wording.

Common HTTP Status Codes You Will Encounter

  • 400 Bad Request – malformed JSON, invalid filter syntax in a Criteria object, or a request that doesn't match the expected schema.
  • 401 Unauthorized – missing, expired, or invalid OAuth2 access tokens. This is the most common error in long-running integrations that don't refresh tokens proactively.
  • 403 Forbidden – the authenticated context lacks the ACL privilege required for the operation, common when using restricted integration users.
  • 404 Not Found – the requested entity ID doesn't exist, or the route itself is invalid.
  • 406 Not Acceptable / 415 Unsupported Media Type – missing or incorrect Accept / Content-Type headers; the Store and Admin APIs are strict about application/json.
  • 422 Unprocessable Entity – the most frequent error when writing data: validation constraints, foreign key violations, or business rule failures from the Data Abstraction Layer.
  • 429 Too Many Requests – rate limiting has kicked in; see the rate limiting guide linked below.
  • 500 Internal Server Error – an unhandled exception on the server. These should be logged with full context and reported, since they usually indicate a plugin conflict or an edge case in custom code.

Handling Errors in a JavaScript Integration

A resilient client should distinguish between retryable and non-retryable failures instead of treating every non-2xx response the same way:

async function callShopwareApi(url, options = {}) {
  const response = await fetch(url, options);

  if (response.ok) {
    return response.json();
  }

  const body = await response.json().catch(() => ({ errors: [] }));
  const errors = body.errors ?? [];

  if (response.status === 401) {
    await refreshAccessToken();
    throw new RetryableApiError('Access token expired', errors);
  }

  if (response.status === 429 || response.status >= 500) {
    throw new RetryableApiError(`Transient failure (${response.status})`, errors);
  }

  if (response.status === 422) {
    const fieldErrors = errors.map(e => ({
      field: e.source?.pointer,
      code: e.code,
      detail: e.detail,
    }));
    throw new ValidationApiError('Validation failed', fieldErrors);
  }

  throw new ApiError(`Unexpected error (${response.status})`, errors);
}

Separating RetryableApiError, ValidationApiError, and a generic ApiError lets calling code decide whether to back off and retry, surface field-level feedback to a user, or fail fast and alert.

Handling Errors When Writing Through the DAL

When writing entities through custom services or plugins on the server side, the DAL throws a WriteException that aggregates every constraint violation from the batch operation, not just the first one:

<?php declare(strict_types=1);

use Shopware\Core\Framework\DataAbstractionLayer\DataAbstractionLayerException;
use Shopware\Core\Framework\Validation\WriteConstraintViolationException;

try {
    $this->productRepository->create($payload, $context);
} catch (WriteConstraintViolationException $exception) {
    foreach ($exception->getViolations() as $violation) {
        $this->logger->warning('Product write validation failed', [
            'property' => $violation->getPropertyPath(),
            'message' => $violation->getMessage(),
        ]);
    }

    throw $exception;
}

Catching the aggregated exception and iterating over getViolations() gives you every failing field in one pass, which is far more useful for debugging bulk import jobs than stopping at the first error.

Building a Central Error Handling Layer

For non-trivial integrations, wrap all API access behind a single client class rather than scattering fetch or Client::request calls throughout the codebase. A central layer gives you one place to normalize error codes, apply retry/backoff policy, and emit consistent structured logs. It also makes it trivial to add circuit-breaker behavior: if a downstream Shopware instance starts returning a burst of 500s, the client can short-circuit further calls for a cooldown period instead of hammering an already struggling server.

Logging and Observability

Every logged API error should capture the code, the HTTP status, the request path, and a correlation ID if your integration generates one. Shopware's own logs (var/log/) will show the corresponding server-side stack trace for 500-level errors, so include enough context on the client side (request ID, timestamp) to correlate the two sides when investigating an incident.

Conclusion

Robust error handling isn't an afterthought bolted onto a finished integration — it's what separates a script that works in a demo from one that survives production traffic, token expiry, and the occasional bad payload. By standardizing on Shopware's code and source.pointer fields, distinguishing retryable from terminal failures, and centralizing API access behind a single client, you can build integrations against the Shopware 6.7 API that degrade gracefully instead of failing silently.