Aller au contenu

omnilex/justice-administrative

Omnilex justice administrative: the decisions of the Conseil d'État, the administrative courts of appeal and the administrative tribunals from their open data (opendata.justice-administrative.fr) - search by text, court, kind and dates, one decision with its full text, what was added since a date - no key.

Étage
Fournisseur · Omnilex
Version
1.x
Suite de conformité
non branchée
Vérifié en réel
Vérifié en réel
Licence
MIT
composer require omnilex/justice-administrative:1.x-dev

The decisions of the Conseil d'État, the administrative courts of appeal and the administrative tribunals for omnisense/omnilex, from the open data of the administrative justice (https://opendata.justice-administrative.fr/). The site documents monthly archives of XML files to download; this source calls the interface the site's own search engine (/recherche) calls - JSON under https://opendata.justice-administrative.fr/recherche/api (JusticeAdministrativeSource::BASE_URI), no published contract, routes seen and checked by real calls on 2026-10-04. There is no sandbox.

What was read first, on 2026-10-04:

https://opendata.justice-administrative.fr/ the site: decisions of the Conseil d'État (since 30 September 2021), of the courts of appeal (31 March 2022), of the tribunals (30 June 2022), added daily
https://opendata.justice-administrative.fr/aide.html the help page: monthly archives of XML files, and their tags (Code_Juridiction, Numero_Dossier, Date_Lecture, Numero_ECLI, Type_Decision, Code_Publication, Formation_Jugement, Texte_Integral...)
/DCE/, /DCA/, /DTA/ the archives: /DCE/2021/06/CE_202106.zip...
https://opendata.justice-administrative.fr/recherche the search engine, whose pages call /recherche/api/... and get JSON
https://opendata.justice-administrative.fr/cgu.html the conditions of use: Licence Ouverte 2.0 and their own rules

Installation

composer require omnilex/justice-administrative

Requires PHP 8.2, omnisense/omnilex (1.x) and symfony/http-client (6.4, 7 or 8). In a Symfony application with OmnilexBundle, the factory is registered as soon as the package is installed.

Credentials

None: the open data is open. Calls carry the User-Agent of the user_agent option.

Options

Option Type Default Required
recent lastModified or Date_Lecture lastModified (JusticeAdministrativeSource::ADDED) no the date recent() reads: the day a decision joined the base, or the day it was read in open court (DECIDED); another value is an InvalidConfigException (which Registry::usable() leaves out)
throttle float 1.0 no seconds between two calls: the interface is the site's own engine, not a service offered to programs
base_uri string https://opendata.justice-administrative.fr/recherche/api no
user_agent string omnilex/1.x (+https://github.com/glitchr-omnisense/omnilex) no

Without an HTTP client, the factory makes one with a 60-second timeout: some routes take a few seconds (20 s were seen for a range of dates over every court).

Capabilities

capabilities(): holds decisions; reads ECLI (the Conseil d'État's); criteria text, number, kind, jurisdictions, from, to, types; jurisdictions FR; 200 per call; pseudonymised.

Operation Supported
search() yes, one call, no paging
decision() yes: a search result's id, a file name, a Conseil d'État ECLI
recent() yes
citations() no: the decisions carry no links to texts nor to other decisions
text(), article() no

Behaviour

Routes. Every route answers {"decisions": {"body": {"hits": {"total": {"value": n}, "hits": [...]}}}}, newest decision first; each hit has an _id (DCE_505010_20261002.xml_CE: the file and the court) and a _source with the tags above plus lastModified. A case number known to several courts comes wrapped in one message holding the decisions: they are unwrapped. The last segment of a search route is the number of results wanted (limit, 1 to 200; the engine's own default is 200). Which route depends on the criteria set:

Criteria Route under /recherche/api/
none, or court and/or kind model_noTyped/openData/<court or null>/<kind or null>/<n>
text Simple_Search/openData/<text>/<n>
text, court model_search_juri/openData/<court>/<text>/<n>
text, kind Check_Search/openData/<kind>/<text>/<n>
text, kind, court model_search_check_juri/openData/<kind>/<court>/<text>/<n>
dates model_Dates/openData/<field>/<from>/<to or false>/<n>
dates, court model_date_juri/openData/<field>/<court>/<from>/<to or false>/<n>
dates, kind (and court) Date_Checkbox/openData/<field>/<kind>/<from>/<to or false>/<court or null>/<n>
text, dates model_searchANDdates/openData/<field>/<text>/<from>/<to or false>/<n>
text, dates, court model_search_date_juri/openData/<field>/<text>/<court>/<from>/<to or false>/<n>
text, dates, kind model_all/openData/<field>/<text>/<kind>/<from>/<to or false>/<n>
text, dates, kind, court model_ABCD/openData/<field>/<text>/<kind>/<court>/<from>/<to or false>/<n>
one decision elastic/decisions/<court>/<file>

<field> is Date_Lecture for search() and the recent option for recent(); <to> is false for "from that day on". A value is one path segment (/ replaced by a space, URL-encoded). A route that answers 404 is a ProviderException: the interface changed.

Query.

text every word: the engine reads bare words as "any of them", so they are sent joined by AND; "...": the exact expression
number a case number (505010, 24LY03335), searched as the text; not along with a text
jurisdictions one court: CE, CAA + department (CAA69: Lyon, CAA13: Marseille), TA + department (TA75: Paris); an order of courts as a whole (CAA, TA) answers nothing
types one of Décision (or decision), Ordonnance
from, to the day the decision was read; to needs from
limit 1 to 200; no paging: Results::$total says how many match, Results::$next is always null
sort not read: newest first

title, subjects, at, a cursor, several courts or kinds, a kind other than DECISION, to without from are refused (NotSupportedException).

A reference. id the hit's _id, title (<court>, <d/m/Y>, <case number>), the ECLI when the court publishes one (the others send the word undefined, skipped), the date read, type (Type_Decision), number, the court, a summary from the engine's highlights, url.

A decision. decision() takes the id of a search result, the file name alone (DCE_505010_20261002, with or without .xml; for a court of appeal or a tribunal the court is found by one more search, by the file's name), or an ECLI of the Conseil d'État (ECLI:FR:CECHS:2026:505010.20261002: it carries the case number, which is searched, and the decision is recognised among the results by its ECLI). Another ECLI or identifier is a NotSupportedException. The Decision holds the court (Conseil d'État with its litigation section as chamber, or the court's name), its formation, the date read, the case number, the kind (type), the classification as publication (Code_Publication: A "Publié au recueil Lebon", B "Mentionné aux tables du recueil Lebon", C "Inédit au recueil Lebon"; other codes as they are), updatedOn (the day it joined the base), the full text as content (the engine's $$$ separators become line breaks), pseudonymised: true, language: fr, and the page that shows it as url (https://opendata.justice-administrative.fr/recherche/shareFile/<court>/<file>). summary, solution, subjects, citations are empty.

Pseudonymisation. The decisions are pseudonymised by the Conseil d'État before they are published: they are given as they are, and nothing here tries to put a name back on a person.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnilex\JusticeAdministrative\JusticeAdministrativeSourceFactory;
use Omnilex\Model\Query;
use Symfony\Component\HttpClient\HttpClient;

$administratif = (new JusticeAdministrativeSourceFactory(HttpClient::create(['timeout' => 60])))->create();

$new = $administratif->recent(new DateTimeImmutable('2026-10-20'), new Query(jurisdictions: ['CE'], limit: 3));
echo $new->total, " decisions of the Conseil d'État added since 2026-10-20; the first 3:\n";
foreach ($new as $reference) {
    echo '  ', $reference->id, ' - ', $reference->title, ' - ', $reference->identifiers->value(Omnilex\Model\Scheme::ECLI) ?? 'no ECLI', "\n";
}
$decision = $administratif->decision($new->items[0]->id);
echo "\n", $decision->title, "\n", $decision->court->name, ' / ', $decision->court->chamber, ' / ', $decision->court->formation, "\n", $decision->type, ', ', $decision->publication, ', added ', $decision->updatedOn?->format('Y-m-d'), "\n", $decision->url, "\n";
echo mb_substr((string) $decision->content, 0, 160), "...\n";
198 decisions of the Conseil d'État added since 2026-10-20; the first 3:
  DCE_516166_20261023.xml_CE - Conseil d'État, 23/10/2026, 516166 - ECLI:FR:CECHS:2026:516166.20261023
  DCE_516364_20261023.xml_CE - Conseil d'État, 23/10/2026, 516364 - ECLI:FR:CECHS:2026:516364.20261023
  DCE_514371_20261022.xml_CE - Conseil d'État, 22/10/2026, 514371 - ECLI:FR:CECHS:2026:514371.20261022

Conseil d'État, 23/10/2026, 516166
Conseil d'État / Section du Contentieux / 7ème chambre jugeant seule
Décision, Inédit au recueil Lebon, added 2026-10-24
https://opendata.justice-administrative.fr/recherche/shareFile/CE/DCE_516166_20261023
Vu la procédure suivante :
Le syndicat CGT d’administration centrale Equipement-Environnement, le syndicat CFDT des territoires, de l’environnement et de la mer...

(called live on 2026-10-26 at 11:56 UTC, from the core's harness image)

In Symfony:

omnilex:
    sources:
        administratif: { factory: justice-administrative }

From the core's harness: docker compose run --rm omnilex recent administratif 2026-10-01 --jurisdiction CE.

Errors

Case Result
an unknown decision null
an identifier it does not read, an ECLI of another court than the Conseil d'État NotSupportedException
a criterion refused (above) NotSupportedException
a route answering 404, an answer without decisions ProviderException
a 5xx, a timeout UnavailableException, never an empty list
a 429 RateLimitedException

Verified, and not

Tested offline: Tests/JusticeAdministrativeSourceTest.php (11 tests), on MockHttpClient with answers recorded on 2026-10-04 (the full texts cut to their first lines): a search by text, court, kind and dates; several words all wanted; each set of criteria on its route; the criteria refused; what one court added since a date; a decision of the Conseil d'État, by its ECLI, and one of the administrative court of appeal of Lyon by its file; an unknown decision; down never an empty list.

Called live: every route of the table on 2026-10-04; the example above on 2026-10-26. No limit is published. The interface has no published contract: a change of the site may break it, and shows as a ProviderException. The monthly archives are not read: download them for a full copy.

Responsibility

An independent, unofficial client, not affiliated with the Conseil d'État. Whoever runs it complies with the open data's conditions of use (cgu.html) and never re-identifies a person: see the README's Responsibility section.

Proposer une modification docs/index.md