adachsoft / public-api-spec
Public API specification validator for PHP libraries
Requires
- php: ^8.3
- adachsoft/console-io: ^0.2
- opis/json-schema: ^2.6
Requires (Dev)
- adachsoft/php-code-style: ^0.4
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.0
- rector/rector: ^2.3
This package is not auto-updated.
Last update: 2026-03-28 12:58:03 UTC
README
public-api.json is a small JSON document that describes the public surface of a PHP library:
- which Composer package it represents,
- since which version the public API is considered stable,
- which contracts from other packages it implements,
- which classes are the main entry points for users,
- whether the entire codebase should be treated as public.
The goal is to provide a tool‑friendly, machine‑readable contract that can be used by humans, static analysis, IDEs and automation (e.g. BC checks, dependency mapping, integration tests).
This repository contains:
- the JSON Schema for the format:
schema/v1/public-api.json, - a PHP validator implementation (under the namespace
AdachSoft\\PublicApiSpec), - a CLI validator exposed as
vendor/bin/public-api-validate, - this documentation explaining how to describe your library using
public-api.json.
1. Where to put public-api.json
Convention:
- File name:
public-api.json - Location in a library repository: repository root (next to
composer.json).
Example project layout:
my-library/
├── composer.json
├── public-api.json <— this file (spec document)
├── src/
└── tests/
This is a convention, not a hard requirement of the schema, but tools will typically assume that public-api.json lives in the project root.
2. Top‑level structure of public-api.json
The JSON Schema for version 1.0 lives in schema/v1/public-api.json. At the top level, the document is an object with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | no | Optional self‑reference to the JSON Schema URL; recommended but not required. |
format_version | string | yes | Version of this specification format (currently only "1.0" is allowed). |
package | string | yes | Composer package name in vendor/package form. |
stable_since | string | yes | First version where the public API is considered stable (SemVer). |
contracts | array | no | List of external contracts that the library implements (see §4). |
entry_points | array | yes | Main entry points of the library (see §5). |
all_code_is_public | boolean | no | Indicates if the entire codebase should be treated as public API. |
notes | string | no | Free‑form notes for humans and AI tools. |
The schema forbids additional properties: you cannot add arbitrary top‑level keys; everything must fit the fields above.
2.1. $schema
Optional, but recommended self‑reference to the schema used to validate the document.
"$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json"
This helps tools discover the schema automatically and enables better editor/IDE support.
2.2. format_version
"format_version": "1.0"
- Type:
string - Constraint: must be exactly
"1.0"for the current schema.
This is not your library version. It is the version of the specification format. Future versions of the spec will use different format_version values ("2.0", etc.).
2.3. package
"package": "vendor/package"
- Type:
string - Pattern:
^[a-z0-9-]+/[a-z0-9-]+$
This is your Composer package name – the same string that appears in composer.json under the name key.
2.4. stable_since
"stable_since": "1.2.0"
- Type:
string - Pattern:
^\d+\.\d+\.\d+(-[a-z0-9]+)?$
This is the first version where your public API is considered stable according to semantic versioning. It is useful for tools that:
- detect BC breaks across versions,
- generate upgrade guides,
- understand which historical versions can be ignored for compatibility checks.
Examples:
"1.0.0"– first stable release."2.1.0-beta"– pre‑release that you still want to treat as the reference for the public surface.
2.5. contracts
"contracts": [
{
"package": "adachsoft/ai-image-contract",
"mappings": [
{
"interface": "AdachSoft\\AiImageContract\\GenerateImageInterface",
"implementation": "Vendor\\ImageLib\\Service\\GenerateImageService"
}
]
}
]
Each contract object has:
package(required):- Type:
string - Meaning: Composer package name of the contract library (interfaces / abstractions).
- Type:
mappings(optional):- Type: array of objects with required fields:
interface: FQCN of the contract interface the library implements.implementation: FQCN of the concrete class that implements the contract.
additionalProperties: false– no extra fields allowed.
- Type: array of objects with required fields:
Use contracts when your library implements contracts from other packages (e.g. PSR interfaces, domain contracts, plugin APIs). This allows tooling to answer questions like:
“Given contract X, which concrete classes implement it in my dependency tree?”
If your library does not implement any external contracts, you can omit contracts or set it to an empty array.
2.6. entry_points
"entry_points": [
{
"type": "service",
"fqn": "Vendor\\Package\\Service\\MyMainService"
},
{
"type": "factory",
"fqn": "Vendor\\Package\\Factory\\ClientFactory"
},
{
"type": "dto",
"fqn": "Vendor\\Package\\Dto\\ImportantResultDto"
}
]
Each entry point object has:
type(required): one of"service""factory""dto""interface""value_object""enum""helper""facade"
fqn(required):- Type:
string - Meaning: Fully qualified class name or static method, e.g.:
"Vendor\\Package\\Service\\MyService""Vendor\\Package\\Factory\\ClientFactory::create"
- Type:
You should list only the most important entry points – the classes that users are expected to construct, inject or call directly. This helps both humans and tooling to understand:
- which APIs are intended for public use,
- where to start reading the code,
- which symbols should be highlighted in documentation.
2.7. all_code_is_public
"all_code_is_public": true
- Type:
boolean - Meaning:
true– treat the entire library as public API.falseor omitted – only the explicitly listed entry points (and the types they expose in public signatures) are public.
Typical usage:
- Contract libraries (interfaces‑only) often set this to
true. - Implementation libraries normally keep it
falseand useentry_pointsto mark the public surface.
2.8. notes
"notes": "This library is designed for AI agents. Breaking changes are announced in the changelog and reflected in public-api.json."
Free‑form text for humans and AI. Use it for:
- high‑level guidance (“This package is low‑level, prefer using X instead”),
- migration notes (“Deprecated methods will be kept until 2.0.0”),
- anything that does not fit into the structured fields.
3. Minimal example
A minimal valid public-api.json that only uses required fields:
{
"$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json",
"format_version": "1.0",
"package": "vendor/package-name",
"stable_since": "1.0.0",
"entry_points": [
{
"type": "service",
"fqn": "Vendor\\Package\\Service\\MainService"
}
]
}
This is already useful for tools that want to:
- discover the main service of the library,
- know when the API became stable,
- verify that the document is structurally valid.
4. Rich example with contracts and multiple entry points
The following example shows a more complete description of a library that implements a contract package and exposes several entry points:
{
"$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json",
"format_version": "1.0",
"package": "acme/image-resizer",
"stable_since": "1.2.0",
"contracts": [
{
"package": "adachsoft/ai-image-contract",
"mappings": [
{
"interface": "AdachSoft\\AiImageContract\\ResizeImageInterface",
"implementation": "Acme\\ImageResizer\\Service\\ResizeImageService"
}
]
}
],
"entry_points": [
{
"type": "service",
"fqn": "Acme\\ImageResizer\\Service\\ResizeImageService"
},
{
"type": "dto",
"fqn": "Acme\\ImageResizer\\Dto\\ResizeOptions"
},
{
"type": "value_object",
"fqn": "Acme\\ImageResizer\\Value\\Size"
},
{
"type": "facade",
"fqn": "Acme\\ImageResizer\\ImageResizerFacade"
}
],
"all_code_is_public": false,
"notes": "The primary entry point is ImageResizerFacade. Low-level services and DTOs may change between minor versions, unless listed here."
}
This document tells tools and readers:
- which contract package the library implements and how interfaces map to implementations,
- which classes represent the public boundary of the library,
- that only those entry points (and their public signatures) should be considered stable,
- since which version (
1.2.0) this guarantee holds.
5. Step‑by‑step: documenting your library
Create
public-api.jsonin the project root.
Start with the minimal structure from §3.Fill in the required metadata:
format_version: always"1.0"for this schema.package: copy fromcomposer.json.stable_since: pick the first version where you are ready to promise API stability.
List your main entry points (
entry_points).- Include the service/facade that most users interact with.
- Add important DTOs, value objects and enums that appear in public signatures.
- Avoid listing every internal helper – keep the list focused.
Describe implemented contracts (
contracts).- For each external contract package you implement, add one object to
contracts. - For each interface you implement from that package, add a mapping with:
interface: contract FQCN,implementation: your concrete FQCN.
- For each external contract package you implement, add one object to
Decide on
all_code_is_public.- For contract libraries and thin wrappers,
truemay be appropriate. - For complex implementation libraries, keep it
falseand rely onentry_points.
- For contract libraries and thin wrappers,
Add
notesfor extra guidance.- Clarify deprecation policy, long‑term support, or special integration rules.
Validate the document.
Use the JSON Schema (or the PHP validator from this package) to ensure thatpublic-api.jsonis valid and consistent.
6. Validating public-api.json with PHP
This repository ships a small PHP library under the namespace AdachSoft\\PublicApiSpec that can be used to validate public-api.json files.
CLI validation
If you install this package as a dependency, you also get a small CLI wrapper around the validator:
vendor/bin/public-api-validate [path/to/public-api.json]
Behavior:
- If you pass a path argument, that file will be validated.
- If you omit the argument, the tool looks for
public-api.jsonin the current working directory. - Output is printed using adachsoft/console-io with colors and icons when supported by the terminal.
Exit codes:
0– the file is valid according to the schema.1– invalid usage or configuration error (malformed JSON, unsupportedformat_version, missing required structural fields).2– validation errors (the document is well-formed JSON but does not satisfy the schema).3– unexpected internal error during validation (I/O problems, unexpected exceptions, etc.).
The rest of this section shows how to call the underlying PHP validator directly.
6.1. Installation
If the package is published on Packagist, you can install it as a normal Composer dependency:
composer require adachsoft/public-api-spec
The library uses opis/json-schema under the hood.
6.2. Basic usage
<?php
declare(strict_types=1);
use AdachSoft\PublicApiSpec\SchemaResolver\LocalSchemaResolver;
use AdachSoft\PublicApiSpec\Validator\OpisSchemaValidator;
use AdachSoft\PublicApiSpec\Exception\InvalidJsonException;
use AdachSoft\PublicApiSpec\Exception\UnsupportedSchemaVersionException;
// Base path where the `schema/` directory from this repo is located
$basePath = __DIR__ . '/vendor/adachsoft/public-api-spec';
$schemaResolver = new LocalSchemaResolver($basePath);
$validator = new OpisSchemaValidator($schemaResolver);
$json = file_get_contents(__DIR__ . '/public-api.json');
try {
$result = $validator->validate($json);
} catch (InvalidJsonException|UnsupportedSchemaVersionException $e) {
// JSON is malformed or uses an unsupported format_version
echo 'Configuration error: ' . $e->getMessage() . PHP_EOL;
exit(1);
}
if (!$result->isValid) {
foreach ($result->errors as $error) {
// $error is an instance of ValidationErrorDto
echo $error->propertyPath . ' : ' . $error->message . PHP_EOL;
}
exit(1);
}
echo "public-api.json is valid.\n";
Key types used above (from this library):
SchemaVersionEnum– internal enum for knownformat_versionvalues (currently onlyV1 = "1.0").LocalSchemaResolver– resolves the JSON Schema fromschema/vN/public-api.jsonon disk.OpisSchemaValidator– validates apublic-api.jsonstring against the appropriate schema version.ValidationResultDto– returned fromvalidate(), with:bool $isValid– overall result,ValidationErrorCollection $errors– typed collection of validation errors.
ValidationErrorDto– contains:string $propertyPath– JSON Pointer path like/entry_points/0/type,/package,string $message– human‑readable description of the problem.
6.3. Error handling
The validator can throw the following exceptions:
InvalidJsonException– the provided string is not a valid JSON document, or the root is not an object, or required structural fields are missing/invalid before schema validation.UnsupportedSchemaVersionException– theformat_versionfield refers to a spec version that this library does not support.SchemaFileNotFoundException(fromLocalSchemaResolver) – the JSON Schema file cannot be found/read on disk.
In all other cases, validation errors are returned via ValidationResultDto and its errors collection.
7. Versioning and compatibility
- The spec format is versioned via
format_version(currently"1.0"). - New spec versions will introduce new JSON Schemas in
schema/v{N}/public-api.jsonand new enum cases inSchemaVersionEnum. - The PHP validator is designed so that adding a new spec version does not require changes in consumer code – it is handled internally based on
format_version.
When you update your library:
- Keep
format_versionat"1.0"as long as you are compatible with this schema. - Update
stable_sinceonly when you change the stability guarantee (e.g. first stable after a beta phase). - Evolve
contractsandentry_pointsas your public API grows, but avoid removing items without a major version bump.
By keeping public-api.json up to date, you give downstream tools and users a clear, machine‑readable picture of your public surface and compatibility promises.