eleven-labs/api-validator
PHP library for validating PSR-7 HTTP requests and responses against OpenAPI/Swagger 2.0 schemas. It checks headers, query parameters, URI, and body using JSON Schema, helping ensure API messages conform to your specification.
This is not a Laravel package. It's a framework-agnostic library that validates PSR-7 requests and responses against a Swagger 2.0 description. There is no service provider, no config file, no auto-discovery, and at 24 stars / 0 dependents you should assume you'll be reading vendor/eleven-labs/api-validator/src rather than Googling for answers.
Minimal path to first use:
docs/api/swagger.json. Validate it in the Swagger Editor before you do anything else. If you only have an OpenAPI 3 spec, stop here; this package cannot read it.Illuminate\Http\Request:
composer require eleven-labs/api-validator nyholm/psr7 symfony/psr-http-message-bridge
// app/Providers/ApiContractServiceProvider.php
public function register(): void
{
$this->app->singleton(\ElevenLabs\Api\Schema::class, fn () => new \ElevenLabs\Api\Schema(
base_path('docs/api/swagger.json')
));
}
$response = $this->getJson('/api/pets/42');
$psrResponse = app(PsrHttpFactory::class)->createResponse($response);
(new \ElevenLabs\Api\Validator\ResponseValidator($schema))->validate($psrResponse);
Where to look first in vendor/: the Schema, Definition and Validator namespaces, then the package's own tests/ directory — for a readme-maturity project, the tests are the documentation. Confirm the actual constructor signatures there before copying any snippet (including these).
Test-time contract assertions (adopt this first). Wrap the validator in a PHPUnit/Pest assertion and use it on your JSON API tests. Response validation is where this library earns its keep: your request shapes are already covered by FormRequests, but nothing stops a controller from returning a field the spec doesn't declare. Run it as a separate contract test suite in CI so it doesn't slow the main suite.
Boundary-guard middleware (adopt second, behind a flag). Convert once, validate, then hand the original Laravel request to the controller:
final class ValidateAgainstSpec
{
public function __construct(
private \ElevenLabs\Api\Schema $schema,
private PsrHttpFactory $psrFactory,
) {}
public function handle(Request $request, Closure $next)
{
$psrRequest = $this->psrFactory->createRequest($request);
try {
$definition = $this->schema->getRequestDefinition(
$psrRequest->getMethod(),
$psrRequest->getUri()->getPath()
);
(new RequestValidator($definition))->validate($psrRequest);
} catch (DefinitionNotFoundException) {
// Undocumented route — log and continue, or 501 if you enforce spec-first.
} catch (ConstraintViolationException $e) {
throw new HttpResponseException(response()->json([
'title' => 'Request does not match the API contract',
'status' => 400,
'violations' => $e->getViolations(),
], 400, ['Content-Type' => 'application/problem+json']));
}
return $next($request);
}
}
Register it as api.spec in bootstrap/app.php / Kernel and attach it only to routes you've fully described. Gate it behind config('api.contract.enforce') so you can turn it off per environment.
Schema-as-singleton for Octane and queue workers. The schema is immutable once built, so a singleton survives between requests under Octane and pays the parse cost once. If parse time still hurts, pre-convert YAML to JSON and json_decode into an array at deploy time, then feed the array to Schema if the constructor allows it.
Contract-first workflow. Treat swagger.json as the source of truth: a CI job runs Spectral/swagger-cli on the spec, a second job runs the contract test suite, and PRs that change behaviour must change the spec. This is the pattern that makes a 24-star dependency worth the risk — you're buying spec enforcement, not a feature.
Per-version schemas. For /api/v1 and /api/v2, register two Schema instances in a small registry keyed by version and resolve in the middleware from the route prefix. Don't try to cram versions into one spec with basePath switching.
Abstract it. Put your own ApiContractValidator interface in front of the library and make the middleware depend on that. Swapping in league/openapi-psr7-validator (OpenAPI 3 capable) later becomes a one-file change.
api-spec-converter is lossy (nullable, oneOf, anyOf, requestBody all get mangled). If your spec is 3.x, this package is the wrong choice.justinrainbow/json-schema parses JSON. Author in YAML if you like, but emit swagger.json as a build artefact and consume that.$psrRequest->getBody() is consumed after validation, so call rewind() if you reuse it. withParsedBody is not populated by the bridge, so anything the validator expects in the parsed body must come from decoding the raw stream. Uploaded files and multipart/form-data bodies are effectively out of scope — exclude those routes.basePath mismatches, trailing slashes, and proxies rewriting the URI all produce a "definition not found" rather than a validation error. Distinguish that from a genuine violation in your error mapping, otherwise misconfigured specs look like client errors.additionalProperties: false is the most common self-inflicted outage. If your spec is strict and a client sends an extra key, the request is rejected in production. Audit before enforcing.format: date-time, uuid, email are strict. PHP's DateTime string output can fail date-time if the spec/validator pair doesn't tolerate offsets. Test fixtures early.justinrainbow/json-schema you resolve. Swagger 2.0 is draft-04; don't expect draft-07 keywords to behave.RequestDefinition and json_encode() the generated schema, then diff it against your spec. Nine times out of ten it's $ref resolution, basePath, or a content-type mismatch.How can I help you explore Laravel packages today?