Slide 1

Slide 1 text

SEPTEMBER 17 -18, 2026 - LILLE, FRANCE & ONLINE Are APIs Still Relevant in the AI Era? Antoine Bluchet

Slide 2

Slide 2 text

Antoine Bluchet aka soyuka ✔ API Platform release manager ✔ Developer, biker, builder ✔ Father of 2 ✔ Free software advocate ✔ CTO at Les-Tilleuls.coop https://github.com/soyuka

Slide 3

Slide 3 text

Web and cloud experts

Slide 4

Slide 4 text

POWERED BY LES-TILLEULS.COOP What we do? Automation & AI Workflows Connectivity & MCP AI-Accelerated Development Product Design & Ownership Connect AI to the system you run. [email protected] AI Architecture & Sovereignty Cloud, AIOps & Kubernetes

Slide 5

Slide 5 text

Mon parc auto DEMO ✔ Maintenance overview ✔ Compatible parts ✔ Service history

Slide 6

Slide 6 text

Behind the interface MCP Agent API Platform Vehicle plate lookup (SIV) TecDoc / PartsAPI Motul Garage data (memory)

Slide 7

Slide 7 text

MCP · Model Context Protocol A standard way for AI applications to use external tools and data. AI application MCP client JSON-RPC SSE MCP server JSON-RPC: named requests, structured results and errors. Tool: a callable function with a name, description and input schema. tools/list → discover · tools/call → execute modelcontextprotocol.io

Slide 8

Slide 8 text

Available parts #[ApiResource( operations: [new GetCollection( uriTemplate: '/cars/{carId}/part-categories/{categoryId}/articles', uriVariables: [ 'carId' => new Link(fromClass: Car::class, identifiers: ['id']), 'categoryId' => new Link(fromClass: PartCategory::class, identifiers: ['id']), ], provider: ArticleCollectionProvider::class, )], )] final class Article {} Automotive is quite complex, for oil filter (parts category) there are several references

Slide 9

Slide 9 text

Available parts parameters: [ 'carType' => new QueryParameter( description: 'TecDoc vehicle type of the carId: PC (default) or CV', schema: [ 'type' => 'string', 'enum' => ['PC', 'CV'], 'default' => 'PC', ], constraints: [ new Assert\Choice(choices: ['PC', 'CV']), ], ), ],

Slide 10

Slide 10 text

HTTP provider ArticleCollectionProvider public function provide( Operation $operation, array $uriVariables = [], array $context = [] ): array { $carType = $operation->getParameters()?->get('carType')?->getValue(); return $this->catalog->articles( (int) $uriVariables['carId'], (int) $uriVariables['categoryId'], CarTypeResolver::fromString($carType), ); }

Slide 11

Slide 11 text

MCP tool #[ApiResource( // … mcp: [ 'list_articles' => new McpToolCollection( title: 'List Articles', description: '…', input: ArticlesInput::class, processor: ArticlesProcessor::class, // … ), ], )]

Slide 12

Slide 12 text

Tool input final class ArticlesInput { public int $carId; #[ApiProperty(schema: [ 'type' => 'array', 'items' => ['type' => 'integer'], 'minItems' => 1, ])] public array $categoryIds = []; // … public string $carType = 'PC'; public array $brands = []; }

Slide 13

Slide 13 text

MCP processor ArticlesProcessor foreach ($data->categoryIds as $categoryId) { $categoryId = (int) $categoryId; $groups[] = ArticleGroup::of( $categoryId, $this->catalog->articles( $data->carId, $categoryId, $carType, ), brands: $data->brands, ); } Several categories, grouped for the agent

Slide 14

Slide 14 text

HTTP and MCP interfaces HTTP resource MCP tool GET collection tools/call · JSON-RPC One category per request Several categories per call Flat article list Articles grouped by category Input DTO → JSON Schema → tool arguments

Slide 15

Slide 15 text

Marion Hurteau MCP : votre API a un nouvel utilisateur (et ce n’est pas un humain)

Slide 16

Slide 16 text

How should I expose my API via MCP?

Slide 17

Slide 17 text

A few possibilites… 1 OpenAPI 2 Hypermedia gateway 3 Manual

Slide 18

Slide 18 text

OpenAPI exposes HTTP operations Example: @ivotoby/openapi-mcp-server Declares 57 tools (10 018 tokens) X

Slide 19

Slide 19 text

APIs We Built Are Meant for Computers How Do We Expose Hypermedia APIs to LLMs? Antoine Bluchet · Kévin Dunglas Research paper: HAL-05630480 github.com/coopTilleuls/hydra-mcp-bridge

Slide 20

Slide 20 text

Discovery starts with the entrypoint 1 read_api_resource({uri: "/"}) {"garageVehicle": "/garage/vehicles"} → 1 new navigation tool loaded 2 read_api_resource({uri: "/garage/vehicles"}) Garage operations become available GET · POST · PATCH · DELETE → + 6 tools total

Slide 21

Slide 21 text

Inside the gateway 1 Read the resource Follow JSON-LD links from the API 2 Read the metadata @context + /docs.jsonld describe types and operations 3 Publish MCP tools Build input schemas; forward calls as HTTP requests tools/list_changed tells the client to refresh its tools

Slide 22

Slide 22 text

Manual tools: define the task #[ApiResource(mcp: [ 'get_service_parts' => new McpTool( description: 'Use carId/carType from lookup_plate.', input: ServicePartsInput::class, processor: ServicePartsProcessor::class, ), ])] Description + input schema + processor

Slide 23

Slide 23 text

On declaring proper tools Identify the vehicle lookup_plate Get parts + fluid specs → get_service_parts Record → maintenance plan_maintenance Prompts: “You serve the user by CALLING TOOLS, never by writing prose.” “A maintenance / ‘what do I need’ turn is not finished until plan_maintenance has recorded the tasks”

Slide 24

Slide 24 text

$registry->registerPrompt( new Prompt( name: 'identify_plate', title: 'Identify a plate', description: 'Look up a French registration plate and offer to save the vehicle to the garage.', arguments: [ new PromptArgument('plate', 'French registration plate, e.g. "AJ-019-XG".', required: true), ], ), [new PromptMessage(Role::User, new TextContent(<<

Slide 25

Slide 25 text

Today · 16:00 · Room 1

Slide 26

Slide 26 text

Which interface for your API? Task-specific control Manual tools + an explicit prompt Expose your API OpenAPI: operation inventory Gateway: progressive Hydra discovery github.com/coopTilleuls/hydra-mcp-bridge Try the gateway

Slide 27

Slide 27 text

Mercure

Slide 28

Slide 28 text

Live UI updates with Mercure API changes publish to Mercure Connected interfaces receive each update over SSE

Slide 29

Slide 29 text

When tools change, MCP must react Tool registry changes New tools appear Capability: listChanged MCP notification notifications/tools/list_changed Client refreshes tools list_changed notifications need a persistent SSE channel.

Slide 30

Slide 30 text

Mercure SSE delegation ✔ PHP is not fitted for long running connections ✔ FrankenPHP embeds mercure ✔ MCP SSE may run directly through mercure Prototype repository ↗ PHP SDK comparison ↗

Slide 31

Slide 31 text

2026: What’s new in API Platform ?

Slide 32

Slide 32 text

Pull requests opened and merged Opened 2024 2025 2026 Jan 1–Sep 13 · api-platform/core 284 242 Merged 343 288 508 429 +76% opened · +77% merged vs 2025

Slide 33

Slide 33 text

Faster code, careful review Faster coding More contributions Correctness · compatibility · maintenance My review standard stays the same More review work

Slide 34

Slide 34 text

New API Platform installer curl -fsSL https://api-platform.com/install.sh | sh api-platform my-api --framework=symfony \ --with-docker --with-pwa Symfony or Laravel Optional Docker, Admin and PWA api-platform.com

Slide 35

Slide 35 text

API PLATFORM Latest changes and upgrades notes 4.4 5.0

Slide 36

Slide 36 text

Live release API Platform 4.4 / 5.0

Slide 37

Slide 37 text

4.4 · Migrate your filters BEFORE #[ApiFilter( SearchFilter::class, properties: ['name' => 'partial'] )] Deprecated in 4.4 · removal planned for 6.0

Slide 38

Slide 38 text

4.4 · One parameter, one filter AFTER #[ApiResource(parameters: [ 'name' => new QueryParameter( filter: new PartialSearchFilter(), ), ])] ?name=api → WHERE name LIKE %api% php bin/console api:upgrade-filter

Slide 39

Slide 39 text

4.4 · New search filters StartSearchFilter → title LIKE 'api%' EndSearchFilter → title LIKE '%platform' WordStartSearchFilter → title LIKE 'api%' OR title LIKE '% api%' Doctrine ORM + MongoDB ODM

Slide 40

Slide 40 text

4.4 · ComparisonFilter new QueryParameter( property: 'quantity', filter: new ComparisonFilter( new ExactFilter(), ), ) ?quantity[gte]=10 → Quantity ≥ 10 ?quantity[lt]=20 → Quantity < 20

Slide 41

Slide 41 text

4.4 · OrFilter 'q' => new QueryParameter( filter: new FreeTextQueryFilter([ 'title' => new OrFilter( new PartialSearchFilter(), ), 'isbn' => new OrFilter(new ExactFilter()), ]), ) ?q=978 → WHERE LOWER(title) LIKE '%978%'OR isbn = '978'

Slide 42

Slide 42 text

4.4 · ChainFilter 'code' => new QueryParameter( filter: new ChainFilter([ new StartSearchFilter(), new EndSearchFilter(), ]), ) ?code=AB → WHERE code LIKE 'AB%' AND code LIKE '%AB'

Slide 43

Slide 43 text

4.4 · Doctrine repository methods #[GetCollection(stateOptions: new Options( repositoryMethod: 'forPublicApi', ))] public function forPublicApi(): QueryBuilder { return $this->createQueryBuilder('v') ->andWhere('v.isPublic = :public') ->setParameter('public', true); } Filtering and pagination still apply.

Slide 44

Slide 44 text

4.4 · Decide when missing means 404 OPERATION-LEVEL CONTROL #[Post( uriTemplate: '/feeders/{id}/feed', read: true, throwOnNotFound: true, )] Provider returns null true: stop with 404 false: let the processor handle it

Slide 45

Slide 45 text

4.4 · HTTP QUERY RFC 10008 QUERY /books Content-Type: application/json { } "name": "api" Idempotent method · filters in the request body Supports JSON or form urlencoded

Slide 46

Slide 46 text

5.0 · QUERY with parameters new Query( parameters: [ 'name' => new QueryParameter( filter: new PartialSearchFilter(), ), ], ) QUERY /books Content-Type: application/json {"name": "api"} QueryParameter reads criteria from the body.

Slide 47

Slide 47 text

5.0 · QUERY as Command new Query( input: SearchInput::class, read: false, deserialize: true, write: true, processor: Search::class, ) final class SearchInput { public string $name; } class Search implements ProcessorInterface { public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): array { return $this->repo->findBy(['name' => $data->name]); } }

Slide 48

Slide 48 text

5.0 · JSON:API identifiers BEFORE · 4.4 compatibility {"data":{"id":"/books/10","type":"Book"}} AFTER · 5.0 default {"data":{"id":"10","type":"Book", "links":{"self":"/books/10"}}} Opt in during 4.4, default to false in 5.0: api_platform: jsonapi: { use_iri_as_id: false }

Slide 49

Slide 49 text

More features… 4.4 · Documentation OpenAPI 3.2 · Scalar · Swagger UI withCredentials 4.4 · JSON-LD / Hydra Resource prefixes · collection member assertions 5.0 · Metadata Charset only where the media type defines it 4.4 · HTTP Parameters on properties · routePriority · container parameters

Slide 50

Slide 50 text

Deprecations and removals 4.4 · Deprecations Legacy filters → QueryParameter Set jsonapi.use_iri_as_id explicitly 5.0 · Removed configuration validator.query_parameter_validation enable_link_security resource_class_directories Legacy filter removals are planned for 6.0.

Slide 51

Slide 51 text

Upgrade checklist Update to 4.4 fix deprecations Update to 5.0 php bin/console api:upgrade-filter https://api-platform.com/docs/core/upgrade-guide/#api-platform-43-to-44

Slide 52

Slide 52 text

OUR RELEASE CADENCE A major every year. 2 years 1 year A mature foundation. Faster development. Same maintenance policy Stable: bug fixes · Old-stable: security fixes

Slide 53

Slide 53 text

ARE APIS STILL RELEVANT IN THE AI ERA? Yes. The interface changes. The API remains. Business rules · permissions · data For applications. For agents.

Slide 54

Slide 54 text

Thank you! Any questions? FOLLOW ME! @s0yuka soyuka.me github.com/soyuka Update prompt: “follow the upgrade guide at https://api-platform.com/docs/core/upgrade-guide/ and update API Platform to 4.4, fix deprecations, then to 5.0”