# uData Portal Extractor (data.gouv.fr & Etalab) (`datamule/udata-portal-extractor`) Actor

Point at ANY uData open-data portal (data.gouv.fr, data.public.lu, ~30 Etalab govs) and extract datasets, organizations, or community reuses via the uniform /api/1/ REST API. Nested resources, facet filters (q/tag/org/license), server-driven pagination.

- **URL**: https://apify.com/datamule/udata-portal-extractor.md
- **Developed by:** [Datamule](https://apify.com/datamule) (community)
- **Categories:** Developer tools, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 data records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## uData Portal Extractor

Point at **any [uData](https://github.com/opendatateam/udata) open-data portal** and get
clean, structured rows. uData (built by Etalab) is the platform behind
[data.gouv.fr](https://www.data.gouv.fr) and roughly **30 francophone / EU government
open-data portals**, and every deployment exposes the same uniform `/api/1/…` REST API — so
this is one generic runner, not a per-portal scraper. Give it a portal base URL and a mode.

> uData is **distinct** from CKAN, Socrata, and OpenDataSoft. Its `/api/1/` shape, its
> 1-indexed `page` / `page_size` pagination, its nested per-dataset `resources[]`, and its
> community **reuses** graph are uData-specific.

### Modes

| `mode` | Endpoint | One row per… |
| --- | --- | --- |
| `datasets` (default) | `/api/1/datasets/` | dataset — with its `resources[]` summarised on (`resourceCount`, first-resource url/format/filesize/mime) and the full lossless `resources` array kept |
| `organizations` | `/api/1/organizations/` | publishing organisation (name, website, dataset/reuse/member counts) |
| `reuses` | `/api/1/reuses/` | community reuse — an app / article / visualisation built on the portal's data (a uData-unique layer) |

With **`emitResources`** on (datasets mode), one extra row per resource — each an actual
downloadable file (url / format / filesize / checksum / mime) — is also emitted, joined to
its parent dataset.

### Facet filters

All passed straight through to the uData API and combinable:

- `query` — full-text `q=` filter
- `tag` — tag facet
- `organization` — an organization id (discover ids via `organizations` mode)
- `license` — license id (e.g. `lov2`, `cc-by`, `odc-odbl`)

### Example input

```json
{
  "portalUrl": "https://www.data.gouv.fr",
  "mode": "datasets",
  "query": "transport",
  "maxRecords": 500,
  "pageSize": 50
}
```

### How it works

- **Server-driven pagination.** uData paginates with a 1-indexed `page` + `page_size`; the
  actor increments `page` and stops the instant the server returns `next_page: null` (or an
  empty page) — never an offset guess, never an over-read. The server's `total` is logged as
  the authoritative count.
- **Nested resources are flattened.** Each dataset's `resources[]` (the actual files) are
  summarised onto the dataset row, with the full array kept losslessly under `resources`.
- **Everything optional is nullable.** Temporal / spatial / frequency / license fields are
  frequently absent and the field set differs between portals, so all three record types
  share one flat, fully-nullable superset schema and every optional field is a `[type, null]`
  union.
- **Honest results.** A reachable portal matching zero records returns zero rows (exit 0) —
  never a fabricated row. A `portalUrl` whose `/api/1/` surface can't be reached at all fails
  loud (exit 91); an unreachable portal is never reported as an empty result.

### Output

One flat record per item. Common fields: `recordType`, `id`, `slug`, `title`, `description`,
`orgName`, `tags`, `license`, `frequency`, `resourceCount`, `firstResourceUrl`,
`firstResourceFormat`, `reuseType`, `topic`, `metricViews`, plus `_portalUrl` / `_mode` /
`_apiUrl` / `_rowIndex` provenance and the lossless `_raw` source object.

### Pricing

Pay-per-event: **$0.0005 per record** (dataset / organization / reuse row, plus resource
rows when `emitResources` is on), with volume discounts across subscription tiers.

### Notes

- These are government servers — keep a descriptive `userAgent`. No auth is required for
  public reads.
- `transport.data.gouv.fr` uses a different specialised `/api/datasets` shape and is **out of
  scope**; this actor targets the standard `/api/1/` uData core.

# Actor input Schema

## `portalUrl` (type: `string`):

Base URL of ANY uData open-data portal (the Etalab platform behind data.gouv.fr and ~30 francophone/EU government portals). The '/api/1/…' path is appended automatically. Examples: https://www.data.gouv.fr (73k+ datasets) · https://data.public.lu (Luxembourg). Note: transport.data.gouv.fr uses a different specialised API shape and is out of scope — this actor targets the standard '/api/1/' uData core.

## `mode` (type: `string`):

What to extract. 'datasets' (default) → one row per dataset with its resources\[] summarised on (resourceCount + first resource url/format/filesize/mime) and the full lossless resources array kept. 'organizations' → one row per publishing organisation. 'reuses' → one row per community reuse (apps/articles/visualisations built on the portal's data — a uData-unique layer).

## `query` (type: `string`):

Optional full-text filter — the uData 'q=' parameter. Applies to whichever mode is selected. e.g. 'transport', 'budget', 'covid'.

## `tag` (type: `string`):

Optional tag facet (datasets/reuses). Only records carrying this tag are returned, e.g. 'transport', 'mobilite-durable'.

## `organization` (type: `string`):

Optional organization id (the uData org id, e.g. '534fff8ba3a7292c64a77de4') to return only datasets/reuses published by that organization. Use 'organizations' mode first to discover ids.

## `license` (type: `string`):

Optional license id facet for datasets, e.g. 'lov2' (Licence Ouverte 2.0), 'cc-by', 'odc-odbl'.

## `emitResources` (type: `boolean`):

In 'datasets' mode, additionally emit ONE extra row per resource (each an actual downloadable file: url/format/filesize/checksum/mime) joined to its parent dataset. Each resource row is billed as a record. Off by default — the dataset row already carries a resource summary + the full lossless resources array.

## `maxRecords` (type: `integer`):

Stop after emitting this many records (dataset/organization/reuse rows, plus resource rows when emitResources is on). Pagination halts early once reached.

## `pageSize` (type: `integer`):

Records requested per API page (uData 'page\_size'). The server caps this; values above 500 are clamped to 500.

## `userAgent` (type: `string`):

Override the default descriptive User-Agent header. These are government servers — please keep a descriptive agent.

## `timeoutSecs` (type: `integer`):

Per-request timeout in seconds for each API page fetch.

## Actor input object example

```json
{
  "portalUrl": "https://www.data.gouv.fr",
  "mode": "datasets",
  "emitResources": false,
  "maxRecords": 2000,
  "pageSize": 50,
  "timeoutSecs": 60
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "portalUrl": "https://www.data.gouv.fr"
};

// Run the Actor and wait for it to finish
const run = await client.actor("datamule/udata-portal-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "portalUrl": "https://www.data.gouv.fr" }

# Run the Actor and wait for it to finish
run = client.actor("datamule/udata-portal-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "portalUrl": "https://www.data.gouv.fr"
}' |
apify call datamule/udata-portal-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=datamule/udata-portal-extractor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/bFfpjLKf5ozHoE9mi/builds/vhwOXvgiZKA6ilQkn/openapi.json
