# Drupal

> Work with Drupal from a workflow, in two halves. The first needs nothing at all: search drupal.org for any of its tens of thousands of modules, themes and distributions, read a project's releases, look through issue queues, and find contributors. The action worth having on a schedule is the security check, which takes the version you run and tells you whether drupal.org has marked it insecure, whether the project has been abandoned, and what to move to. The second half talks to a Drupal site of your own over JSON:API, which ships in core: list, read, create, change and delete content, taxonomy terms, users and anything else in its content model, with a raw request action for the rest. Add the site URL and an account to switch that half on; everything about drupal.org keeps working without one.

App · Website Builders · 14 actions. Web page: https://docs.lodol.com/docs/integrations/drupal

## Actions

### Browse projects

Browse the modules, themes and distributions published on drupal.org, newest first by default. Use it to watch what is being released, to build a digest of new contributions, or to page through a category. Note that drupal.org has tens of thousands of projects, so this pages rather than returning everything.

**Inputs**

- Kind of project (`project_type`, text): Which kind to list. Defaults to modules. One of: `project_module` (Modules), `project_theme` (Themes), `project_distribution` (Distributions), `project_core` (Drupal core). Default: `project_module`.
- Newest first (`newest_first`, true/false): Sort by when the project was first published, newest first. On by default. Turn it off for drupal.org's own default order. Default: `true`.
- How many to return (`size`, number): How many rows to return, from 1 to 100. Defaults to 25. From 1 to 100. Default: `25`.
- Page (`page`, number): Which page of results to return, counting from 0. Use it with 'How many to return' to walk through a long list. From 0 to 100000. Default: `0`.

**Outputs**

- `count` (number): How many rows came back
- `pages_available` (number): How many pages exist at this page size
- `project_type` (text): The kind that was listed
- `projects` (list)

**Example output**

```json
{
  "count": 2,
  "pages_available": 28198,
  "project_type": "project_module",
  "projects": [
    {
      "changed": "1789669929",
      "created": "1789669929",
      "has_issue_queue": true,
      "has_releases": true,
      "homepage": null,
      "machine_name": "paragraph_display_toggle",
      "maintainer": "studgate",
      "node_id": "3624102",
      "summary": "<p>Paragraph Display Toggle lets an editor hide a single paragraph from public pages without deleting it. The paragraph keeps its content, stays editable, and can be switched back on at any time.</p>...",
      "title": "Paragraph Display Toggle",
      "type": "project_module",
      "url": "https://www.drupal.org/project/paragraph_display_toggle"
    },
    {
      "changed": "1789670273",
      "created": "1789669580",
      "has_issue_queue": true,
      "has_releases": true,
      "homepage": null,
      "machine_name": "ai_provider_typesafeai",
      "maintainer": "markusa",
      "node_id": "3624098",
      "summary": "<p>TypeSafe AI Provider connects the <a href=\"https://www.drupal.org/project/ai\" rel=\"nofollow\">AI module</a> to <a href=\"https://typesafe.ai\" rel=\"nofollow\">TypeSafe AI</a> System One models, startin...",
      "title": "AI Provider TypeSafe AI",
      "type": "project_module",
      "url": "https://www.drupal.org/project/ai_provider_typesafeai"
    }
  ]
}
```

### Check for security updates

Ask whether a project needs updating, and whether it is urgent. Give it the version you run and it says plainly whether that release is one drupal.org has marked insecure, whether the project has been abandoned, and what to move to. Being on a release marked insecure is the thing to act on, and no version number can tell you that by itself. Run it over your module list on a schedule to get a standing security check.

**Inputs**

- Project (`project`, text, required): The module, theme or distribution, by its machine name: the short name in its drupal.org URL, so drupal.org/project/pathauto is 'pathauto'. The full URL is accepted too.
- Version you are running (`installed_version`, text): The version string exactly as Drupal reports it, for example '8.x-1.14' or '11.4.5'. Leave blank to just be told the current release.
- Drupal version (`core_version`, text): Which Drupal version's releases to read, for example '11.x' or '7.x'. Leave blank for 'current', which is what the project publishes today. Default: `current`.

**Outputs**

- `installed_is_insecure` (true/false): True when the installed release is one a security release fixed
- `installed_is_known` (true/false): False when drupal.org has no such release
- `installed_version` (text): The version that was checked, if one was given
- `is_unsupported` (true/false): True when drupal.org no longer covers it with security advisories
- `latest_release` (object)
- `latest_security_release` (object)
- `latest_version` (text): Newest stable release
- `needs_attention` (true/false): True when anything above warrants acting on. The single flag to branch on
- `project` (text): Machine name that was checked
- `project_status` (text): 'published', 'unsupported' or 'obsolete'
- `releases_checked` (number): How many releases were examined
- `summary` (text): One line saying what the answer means
- `title` (text): Human readable project name
- `update_available` (true/false): True when a newer stable release exists
- `url` (text): The project page on drupal.org

**Example output**

```json
{
  "installed_is_insecure": true,
  "installed_is_known": true,
  "installed_version": "11.4.5",
  "is_unsupported": false,
  "latest_release": {
    "date": "1789571969",
    "download_url": "https://ftp.drupal.org/files/projects/drupal-11.4.7.tar.gz",
    "is_insecure": false,
    "is_security_release": true,
    "name": "drupal 11.4.7",
    "release_types": [
      "Security update"
    ],
    "security_note": "Covered by Drupal's security advisory policy",
    "status": "published",
    "url": "https://www.drupal.org/project/drupal/releases/11.4.7",
    "version": "11.4.7"
  },
  "latest_security_release": {
    "date": "1789571969",
    "download_url": "https://ftp.drupal.org/files/projects/drupal-11.4.7.tar.gz",
    "is_insecure": false,
    "is_security_release": true,
    "name": "drupal 11.4.7",
    "release_types": [
      "Security update"
    ],
    "security_note": "Covered by Drupal's security advisory policy",
    "status": "published",
    "url": "https://www.drupal.org/project/drupal/releases/11.4.7",
    "version": "11.4.7"
  },
  "latest_version": "11.4.7",
  "needs_attention": true,
  "project": "drupal",
  "project_status": "published",
  "releases_checked": 559,
  "summary": "drupal 11.4.5 is marked insecure on drupal.org. Update to 11.4.7.",
  "title": "Drupal core",
  "update_available": true,
  "url": "https://www.drupal.org/project/drupal"
}
```

### Create a record

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. Create a record on the site: publish an article, add a taxonomy term, file anything the content model allows. The fields go in as a JSON object named the way the site names them, which 'Get a record' will show you for something that already exists. Creating needs an account with permission to do it.

**Inputs**

- Resource type (`resource_type`, text, required): What to read or write, written entity--bundle. 'node--article' is the Article content type, 'taxonomy_term--tags' the Tags vocabulary. Which types a site has depends on its content model, so run 'List what the site exposes' against a site you do not already know.
- Fields (`attributes`, object, required): The fields to set, as a JSON object. Text fields with a format take an object of their own, so a body reads {"body": {"value": "Hello", "format": "basic_html"}}.
- Links to other records (`relationships`, object): Relationships to set, as a JSON object in JSON:API's own shape. Each entry names the type and the UUID of what it points at.

**Outputs**

- `attributes` (object): Every field on the record, exactly as the site names them
- `changed` (text): When it last changed
- `created` (text): When it was created
- `drupal_internal_id` (number): The node, term or user id shown in the admin screens
- `id` (text): The record's UUID
- `relationships` (object): Links to other records, each as a type and an id
- `resource_type` (text): Its entity--bundle type
- `status` (true/false): Whether it is published
- `title` (text): Title or name, where the type has one
- `url` (text): Its own JSON:API address

**Example output**

```json
{
  "attributes": {
    "body": {
      "value": "text"
    },
    "created": "2026-09-17T10:00:00+00:00",
    "drupal_internal__nid": 42,
    "status": true,
    "title": "Created"
  },
  "changed": null,
  "created": "2026-09-17T10:00:00+00:00",
  "drupal_internal_id": 42,
  "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
  "relationships": {
    "uid": {
      "id": "u-1",
      "type": "user--user"
    }
  },
  "resource_type": "node--article",
  "status": true,
  "title": "Created",
  "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
}
```

### Delete a record

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. Delete a record from the site permanently. Drupal does not put JSON:API deletions in a trash can, so this cannot be undone from here: unpublish it with 'Update a record' instead if you might want it back.

**Inputs**

- Resource type (`resource_type`, text, required): What to read or write, written entity--bundle. 'node--article' is the Article content type, 'taxonomy_term--tags' the Tags vocabulary. Which types a site has depends on its content model, so run 'List what the site exposes' against a site you do not already know.
- Record id (`record_id`, text, required): The record's UUID, which is what JSON:API identifies things by rather than the node id shown in the admin screens. Every list action returns one per row.

**Outputs**

- `deleted` (true/false): True when the site confirmed the deletion
- `id` (text): UUID of the record that was deleted
- `resource_type` (text): The type that was deleted from
- `site_url` (text): The site it was deleted from

**Example output**

```json
{
  "deleted": true,
  "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
  "resource_type": "node--article",
  "site_url": "http://127.0.0.1:8731"
}
```

### Find a project

Look up a module, theme or distribution on drupal.org by its machine name and get what it is, who maintains it, whether it takes issues and whether it has releases. Paste the project URL and the name is taken out of it. This is the first step for anything else that names a project.

**Inputs**

- Project (`project`, text, required): The module, theme or distribution, by its machine name: the short name in its drupal.org URL, so drupal.org/project/pathauto is 'pathauto'. The full URL is accepted too.

**Outputs**

- `changed` (text): When it last changed
- `created` (text): When the project was first published
- `has_issue_queue` (true/false): Whether it takes issues
- `has_releases` (true/false): Whether it has published releases
- `homepage` (text): The project's own site, if it has one
- `machine_name` (text): Short name used in the URL
- `maintainer` (text): Username of the project owner
- `node_id` (text): drupal.org node id for the project
- `summary` (text): Opening of the project description
- `title` (text): Human readable project name
- `type` (text): project_module, project_theme, project_distribution or project_core
- `url` (text): The project page on drupal.org

**Example output**

```json
{
  "changed": "1770576519",
  "created": "1108425985",
  "has_issue_queue": true,
  "has_releases": true,
  "homepage": "http://groups.drupal.org/paths",
  "machine_name": "pathauto",
  "maintainer": "dave reid",
  "node_id": "17345",
  "summary": "<p>The Pathauto module automatically generates URL/path aliases for various kinds of content (nodes, taxonomy terms, users) without requiring the user to manually specify the path alias. This allows y...",
  "title": "Pathauto",
  "type": "project_module",
  "url": "https://www.drupal.org/project/pathauto"
}
```

### Get a contributor

Look up a drupal.org contributor by username and get their profile: when they joined, where they are, what they say they work on and which organisations they are attached to. Use it to put a name to a maintainer or a patch author.

**Inputs**

- Username (`username`, text, required): The drupal.org username, which is the name in the profile URL: drupal.org/u/webchick is 'webchick'. Case sensitive.

**Outputs**

- `areas_of_expertise` (list): What they say they work on
- `biography` (text): Opening of their profile text
- `country` (text): Two-letter country code, where they gave one
- `created` (text): When they joined, as a Unix timestamp
- `first_name` (text): First name, where they gave one
- `last_name` (text): Last name, where they gave one
- `organizations` (list): Organisations on their profile
- `url` (text): Their profile page
- `user_id` (text): Their numeric user id
- `username` (text): Their drupal.org username

**Example output**

```json
{
  "areas_of_expertise": [
    {
      "id": "151101",
      "resource": "taxonomy_term",
      "uri": "https://www.drupal.org/api-d7/taxonomy_term/151101"
    },
    {
      "id": "151449",
      "resource": "taxonomy_term",
      "uri": "https://www.drupal.org/api-d7/taxonomy_term/151449"
    }
  ],
  "biography": null,
  "country": "CA",
  "created": "1117921746",
  "first_name": "Angie",
  "last_name": "Byron",
  "organizations": [
    {
      "id": "638519",
      "resource": "field_collection_item",
      "uri": "https://www.drupal.org/api-d7/field_collection_item/638519"
    },
    {
      "id": "1498159",
      "resource": "field_collection_item",
      "uri": "https://www.drupal.org/api-d7/field_collection_item/1498159"
    }
  ],
  "url": "https://www.drupal.org/u/webchick",
  "user_id": "24967",
  "username": "webchick"
}
```

### Get a record

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. Read one record from the site by its UUID, with every field it carries. JSON:API identifies records by UUID rather than by the node id the admin screens show, and every list action returns one per row.

**Inputs**

- Resource type (`resource_type`, text, required): What to read or write, written entity--bundle. 'node--article' is the Article content type, 'taxonomy_term--tags' the Tags vocabulary. Which types a site has depends on its content model, so run 'List what the site exposes' against a site you do not already know.
- Record id (`record_id`, text, required): The record's UUID, which is what JSON:API identifies things by rather than the node id shown in the admin screens. Every list action returns one per row.
- Also fetch related records (`include`, text): Comma-separated relationships to pull in alongside the record, for example 'uid,field_tags'. Saves a second call when you need the author's name or a term's label rather than just its id.

**Outputs**

- `attributes` (object): Every field on the record, exactly as the site names them
- `changed` (text): When it last changed
- `created` (text): When it was created
- `drupal_internal_id` (number): The node, term or user id shown in the admin screens
- `id` (text): The record's UUID
- `relationships` (object): Links to other records, each as a type and an id
- `resource_type` (text): Its entity--bundle type
- `status` (true/false): Whether it is published
- `title` (text): Title or name, where the type has one
- `url` (text): Its own JSON:API address

**Example output**

```json
{
  "attributes": {
    "body": {
      "value": "text"
    },
    "created": "2026-09-17T10:00:00+00:00",
    "drupal_internal__nid": 42,
    "status": true,
    "title": "Hello"
  },
  "changed": null,
  "created": "2026-09-17T10:00:00+00:00",
  "drupal_internal_id": 42,
  "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
  "relationships": {
    "uid": {
      "id": "u-1",
      "type": "user--user"
    }
  },
  "resource_type": "node--article",
  "status": true,
  "title": "Hello",
  "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
}
```

### Get an issue

Read one issue from drupal.org by its node id, the number in its URL. Returns the title, status, priority, category, who filed it, who it is assigned to and the opening of the description, so a workflow can post it to chat or open a matching ticket somewhere else.

**Inputs**

- Issue id (`issue_id`, number, required): The number in the issue's URL, so drupal.org/node/3588582 is 3588582. The search action returns one per row. At least 1.

**Outputs**

- `assigned_to` (text): Username it is assigned to, if anyone
- `body` (text): Opening of the issue description
- `category` (text): Bug report, Task, Feature request, Support request or Plan
- `changed` (text): When it last changed
- `comment_count` (number): How many comments it has
- `component` (text): Which part of the project it concerns
- `created` (text): When it was filed
- `node_id` (text): drupal.org node id for the issue
- `priority` (text): Critical, Major, Normal or Minor
- `project_node_id` (text): Node id of the project it belongs to
- `reporter` (text): Username who filed it
- `status` (text): Readable status, e.g. 'Needs review'
- `status_id` (text): The numeric status the API filters on
- `title` (text): Issue title
- `url` (text): The issue on drupal.org
- `version` (text): Version the issue was filed against

**Example output**

```json
{
  "assigned_to": null,
  "body": "<h3 id=\"summary-problem-motivation\">Problem/Motivation</h3>\n<p>The upgrade from 8.x-1.14 to 8.x-1.15 appears to have changed the selection_criteria in the Content Type pattern YAML from an array to a...",
  "category": "Bug report",
  "changed": "1788423870",
  "comment_count": "12",
  "component": "Code",
  "created": "1778044413",
  "node_id": "3588582",
  "priority": "Normal",
  "project_node_id": "17345",
  "reporter": "kenwest",
  "status": "Needs review",
  "status_id": "8",
  "title": "Selection criteria duplicated when editing pattern for a content type",
  "url": "https://www.drupal.org/project/pathauto/issues/3588582",
  "version": "8.x-1.15"
}
```

### List a project's releases

List the releases published for a module, theme or Drupal core, newest first, each with its download link and how drupal.org labels it. Use it to build a changelog, to find the download URL for a particular version, or to see how actively a project is maintained.

**Inputs**

- Project (`project`, text, required): The module, theme or distribution, by its machine name: the short name in its drupal.org URL, so drupal.org/project/pathauto is 'pathauto'. The full URL is accepted too.
- Drupal version (`core_version`, text): Which Drupal version's releases to read, for example '11.x' or '7.x'. Leave blank for 'current', which is what the project publishes today. Default: `current`.
- How many to return (`size`, number): How many releases to return, from 1 to 500. Defaults to 25. Drupal core has hundreds. From 1 to 500. Default: `25`.

**Outputs**

- `count` (number): How many were returned
- `project` (text): Machine name that was read
- `project_status` (text): 'published', 'unsupported' or 'obsolete'
- `releases` (list)
- `title` (text): Human readable project name
- `total` (number): How many releases the feed carried
- `url` (text): The project page on drupal.org

**Example output**

```json
{
  "count": 2,
  "project": "pathauto",
  "project_status": "published",
  "releases": [
    {
      "date": "1777931039",
      "download_url": "https://ftp.drupal.org/files/projects/pathauto-8.x-1.15.tar.gz",
      "is_insecure": false,
      "is_security_release": false,
      "name": "pathauto 8.x-1.15",
      "release_types": [
        "Bug fixes",
        "New features"
      ],
      "security_note": "Covered by Drupal's security advisory policy",
      "status": "published",
      "url": "https://www.drupal.org/project/pathauto/releases/8.x-1.15",
      "version": "8.x-1.15"
    },
    {
      "date": "1759838097",
      "download_url": "https://ftp.drupal.org/files/projects/pathauto-8.x-1.14.tar.gz",
      "is_insecure": false,
      "is_security_release": false,
      "name": "pathauto 8.x-1.14",
      "release_types": [
        "Bug fixes"
      ],
      "security_note": "Covered by Drupal's security advisory policy",
      "status": "published",
      "url": "https://www.drupal.org/project/pathauto/releases/8.x-1.14",
      "version": "8.x-1.14"
    }
  ],
  "title": "Pathauto",
  "total": 25,
  "url": "https://www.drupal.org/project/pathauto"
}
```

### List records

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. List records of one type from the site: articles, pages, taxonomy terms, users, or anything else its content model has. Filter on a field, sort, and pull in related records in the same call. The attributes come back exactly as the site names them, because a site's fields are its own.

**Inputs**

- Resource type (`resource_type`, text, required): What to read or write, written entity--bundle. 'node--article' is the Article content type, 'taxonomy_term--tags' the Tags vocabulary. Which types a site has depends on its content model, so run 'List what the site exposes' against a site you do not already know.
- Filters (`filters`, object): Field and value pairs to match on, as a JSON object, for example {"status": 1} for published only, or {"title": "Welcome"}. Each pair is an exact match. Use 'Run a JSON:API request' for anything more involved.
- Sort by (`sort`, text): Field to sort on. Prefix with a minus for descending, so '-created' is newest first.
- Also fetch related records (`include`, text): Comma-separated relationships to pull in alongside the record, for example 'uid,field_tags'. Saves a second call when you need the author's name or a term's label rather than just its id.
- How many to return (`size`, number): How many records to return, from 1 to 50. Defaults to 25. Drupal enforces the ceiling itself. From 1 to 50. Default: `25`.
- Skip this many (`offset`, number): How many records to skip before returning any. Use it with 'How many to return' to walk a long list. From 0 to 10000000. Default: `0`.

**Outputs**

- `count` (number): How many records came back
- `has_more` (true/false): Whether there is another page after this one
- `records` (list)
- `records_withheld` (true/false): Present and true when the site withheld some records the account may not read. The list is short rather than wrong
- `resource_type` (text): The type that was listed
- `total` (number): How many exist in total, when the site reports it

**Example output**

```json
{
  "count": 2,
  "has_more": true,
  "records": [
    {
      "attributes": {
        "body": {
          "value": "text"
        },
        "created": "2026-09-17T10:00:00+00:00",
        "drupal_internal__nid": 42,
        "status": true,
        "title": "Hello"
      },
      "changed": null,
      "created": "2026-09-17T10:00:00+00:00",
      "drupal_internal_id": 42,
      "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
      "relationships": {
        "uid": {
          "id": "u-1",
          "type": "user--user"
        }
      },
      "resource_type": "node--article",
      "status": true,
      "title": "Hello",
      "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
    },
    {
      "attributes": {
        "body": {
          "value": "text"
        },
        "created": "2026-09-17T10:00:00+00:00",
        "drupal_internal__nid": 42,
        "status": true,
        "title": "Second"
      },
      "changed": null,
      "created": "2026-09-17T10:00:00+00:00",
      "drupal_internal_id": 42,
      "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
      "relationships": {
        "uid": {
          "id": "u-1",
          "type": "user--user"
        }
      },
      "resource_type": "node--article",
      "status": true,
      "title": "Second",
      "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
    }
  ],
  "resource_type": "node--article",
  "total": 2
}
```

### List what the site exposes

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. List every resource type the connected site publishes over JSON:API, each written entity--bundle. Which types a site has depends entirely on its content model, so run this first against a site you do not already know: every other site action needs one of these names.

**Inputs**: None.

**Outputs**

- `authenticated` (true/false): Whether credentials were sent. Anonymous sees fewer types
- `count` (number): How many types came back
- `jsonapi_version` (text): JSON:API version the site speaks
- `resource_types` (list)
- `site_url` (text): The site that answered

**Example output**

```json
{
  "authenticated": true,
  "count": 2,
  "jsonapi_version": "1.1",
  "resource_types": [
    {
      "resource_type": "node--article",
      "url": "http://127.0.0.1:8731/jsonapi/node/article"
    },
    {
      "resource_type": "taxonomy_term--tags",
      "url": "http://127.0.0.1:8731/jsonapi/taxonomy_term/tags"
    }
  ],
  "site_url": "http://127.0.0.1:8731"
}
```

### Run a JSON:API request

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. Call any JSON:API endpoint on the site directly. The escape hatch for the parts of a content model the other actions do not reach: a custom entity type, a filter too involved for the simple field-and-value form, a relationship endpoint, or a revision. The response is shaped the same way whether the endpoint returns one record or many.

**Inputs**

- Path (`path`, text, required): The JSON:API path to call, with or without the /jsonapi prefix, for example 'node/article' or '/jsonapi/node/article/<uuid>/field_tags'.
- Method (`method`, text): Which HTTP method to use. Defaults to GET. One of: `GET` (GET (read)), `POST` (POST (create)), `PATCH` (PATCH (change)), `DELETE` (DELETE (remove)). Default: `GET`.
- Query parameters (`query`, object): Query string parameters as a JSON object, passed through untouched. This is where a filter JSON:API's shorthand cannot express goes.
- Request body (`body`, object): The request body as a JSON object, in JSON:API's own shape. Ignored on a GET.

**Outputs**

- `count` (number): How many records came back
- `has_more` (true/false): Whether another page follows
- `method` (text): The method that was used
- `path` (text): The path that was called
- `raw` (object): The untouched response, when it carried no records
- `record` (object): One record from the site, with its JSON:API envelope flattened
- `records` (list)
- `site_url` (text): The site that answered
- `total` (number): How many exist in total, when the site reports it

**Example output**

```json
{
  "count": 2,
  "has_more": true,
  "method": "GET",
  "path": "node/article",
  "records": [
    {
      "attributes": {
        "body": {
          "value": "text"
        },
        "created": "2026-09-17T10:00:00+00:00",
        "drupal_internal__nid": 42,
        "status": true,
        "title": "Hello"
      },
      "changed": null,
      "created": "2026-09-17T10:00:00+00:00",
      "drupal_internal_id": 42,
      "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
      "relationships": {
        "uid": {
          "id": "u-1",
          "type": "user--user"
        }
      },
      "resource_type": "node--article",
      "status": true,
      "title": "Hello",
      "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
    },
    {
      "attributes": {
        "body": {
          "value": "text"
        },
        "created": "2026-09-17T10:00:00+00:00",
        "drupal_internal__nid": 42,
        "status": true,
        "title": "Second"
      },
      "changed": null,
      "created": "2026-09-17T10:00:00+00:00",
      "drupal_internal_id": 42,
      "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
      "relationships": {
        "uid": {
          "id": "u-1",
          "type": "user--user"
        }
      },
      "resource_type": "node--article",
      "status": true,
      "title": "Second",
      "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
    }
  ],
  "site_url": "http://127.0.0.1:8731",
  "total": 2
}
```

### Search issues

List issues from a project's queue, or across all of drupal.org, most recently changed first. Filter by status, priority and category to watch what needs review on a module you maintain, to find critical bugs before an upgrade, or to feed a triage report.

**Inputs**

- Project (`project`, text): Narrow to one project's queue, by machine name or URL. Leave blank to search every queue on drupal.org at once.
- Status (`status`, text): Only issues at this status. Leave blank for any. One of: `1` (Active), `18` (Closed (cannot reproduce)), `3` (Closed (duplicate)), `7` (Closed (fixed)), `vs` (Closed (outdated)), `5` (Closed (won't fix)), `6` (Closed (works as designed)), `2` (Fixed), `8` (Needs review), `13` (Needs work), and 4 more.
- Priority (`priority`, text): Only issues at this priority. Leave blank for any. One of: `400` (Critical), `300` (Major), `200` (Normal), `100` (Minor).
- Category (`category`, text): Only issues of this kind. Leave blank for any. One of: `1` (Bug report), `2` (Task), `3` (Feature request), `4` (Support request), `5` (Plan).
- How many to return (`size`, number): How many rows to return, from 1 to 100. Defaults to 25. From 1 to 100. Default: `25`.
- Page (`page`, number): Which page of results to return, counting from 0. Use it with 'How many to return' to walk through a long list. From 0 to 100000. Default: `0`.

**Outputs**

- `count` (number): How many rows came back
- `issues` (list)
- `pages_available` (number): How many pages exist at this page size
- `project` (text): Machine name the search was narrowed to, if any

**Example output**

```json
{
  "count": 2,
  "issues": [
    {
      "assigned_to": null,
      "body": "<h3 id=\"summary-problem-motivation\">Problem/Motivation</h3>\n<p>The upgrade from 8.x-1.14 to 8.x-1.15 appears to have changed the selection_criteria in the Content Type pattern YAML from an array to a...",
      "category": "Bug report",
      "changed": "1788423870",
      "comment_count": "12",
      "component": "Code",
      "created": "1778044413",
      "node_id": "3588582",
      "priority": "Normal",
      "project_node_id": "17345",
      "reporter": "kenwest",
      "status": "Needs review",
      "status_id": "8",
      "title": "Selection criteria duplicated when editing pattern for a content type",
      "url": "https://www.drupal.org/project/pathauto/issues/3588582",
      "version": "8.x-1.15"
    },
    {
      "assigned_to": null,
      "body": "<h3 id=\"summary-problem-motivation\">Problem/Motivation</h3>\n<p>Deprecated function: Using null as an array offset is deprecated, use an empty string instead in Drupal\\pathauto\\PathautoGenerator-&gt;ge...",
      "category": "Task",
      "changed": "1786444648",
      "comment_count": "3",
      "component": "Code",
      "created": "1786441842",
      "node_id": "3616056",
      "priority": "Normal",
      "project_node_id": "17345",
      "reporter": "trickfun",
      "status": "Needs review",
      "status_id": "8",
      "title": "Deprecated function: Using null as an array offset is deprecated, use an empty string instead in Drupal\\pathauto\\PathautoGenerator->getPatternByEntity()",
      "url": "https://www.drupal.org/project/pathauto/issues/3616056",
      "version": "8.x-1.15"
    }
  ],
  "pages_available": 16,
  "project": "pathauto"
}
```

### Update a record

This acts on the Drupal site connected to this integration, so it needs the site URL, and credentials for anything the site does not let anonymous visitors do. Change fields on a record that already exists. Only the fields given are touched; everything else is left as it was. Use it to publish or unpublish something, correct a title, or retag an article.

**Inputs**

- Resource type (`resource_type`, text, required): What to read or write, written entity--bundle. 'node--article' is the Article content type, 'taxonomy_term--tags' the Tags vocabulary. Which types a site has depends on its content model, so run 'List what the site exposes' against a site you do not already know.
- Record id (`record_id`, text, required): The record's UUID, which is what JSON:API identifies things by rather than the node id shown in the admin screens. Every list action returns one per row.
- Fields to change (`attributes`, object): The fields to change, as a JSON object. Anything left out is untouched.
- Links to change (`relationships`, object): Relationships to change, as a JSON object in JSON:API's own shape. Anything left out is untouched.

**Outputs**

- `attributes` (object): Every field on the record, exactly as the site names them
- `changed` (text): When it last changed
- `created` (text): When it was created
- `drupal_internal_id` (number): The node, term or user id shown in the admin screens
- `id` (text): The record's UUID
- `relationships` (object): Links to other records, each as a type and an id
- `resource_type` (text): Its entity--bundle type
- `status` (true/false): Whether it is published
- `title` (text): Title or name, where the type has one
- `url` (text): Its own JSON:API address

**Example output**

```json
{
  "attributes": {
    "body": {
      "value": "text"
    },
    "created": "2026-09-17T10:00:00+00:00",
    "drupal_internal__nid": 42,
    "status": true,
    "title": "Updated"
  },
  "changed": null,
  "created": "2026-09-17T10:00:00+00:00",
  "drupal_internal_id": 42,
  "id": "1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e",
  "relationships": {
    "uid": {
      "id": "u-1",
      "type": "user--user"
    }
  },
  "resource_type": "node--article",
  "status": true,
  "title": "Updated",
  "url": "http://127.0.0.1:8731/jsonapi/node/article/1f8a1c4e-0f8b-4a35-9f0e-2d3c4b5a6d7e"
}
```
