ninorai/ninorai-drupal

Receives AI-generated SEO articles from the NinorAI pipeline — via the REST/OAuth2 API or by importing an article bundle (.zip) — and stores them as ninorai nodes.

Maintainers

Package info

github.com/Ninorai-Ai/ninorai-drupal

Homepage

Type:drupal-module

pkg:composer/ninorai/ninorai-drupal

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.1 2026-08-09 11:31 UTC

This package is auto-updated.

Last update: 2026-08-09 12:06:36 UTC


README

Receives AI-generated SEO articles from the NinorAI pipeline and stores them as ninorai nodes. It is the Drupal counterpart of the NinorAI WordPress plugin.

There are two ways to get an article into Drupal:

  1. API push — NinorAI authenticates (OAuth2 bearer token, or HTTP Basic Auth as a fallback) and posts the article as JSON, or uploads the whole bundle .zip in one request.
  2. Manual bundle import — an editor downloads the bundle .zip from NinorAI and uploads it on an admin page. No connection or credentials needed.

Either way the article is created as an unpublished draft so an editor (or the scheduled-publish cron) takes it live.

Requirements

  • Drupal 10 or 11.
  • Core: node, taxonomy, file, image, path, rest, basic_auth, serialization, datetime.
  • Contrib (for OAuth2): simple_oauth and consumers — pulled in automatically by composer.

Installation

composer require ninorai/ninorai-drupal
drush en ninorai

If the package is not yet visible on Packagist for you, add the repository directly to your project's composer.json first:

"repositories": [
    { "type": "vcs", "url": "https://github.com/Ninorai-Ai/ninorai-drupal" }
]

Authentication setup

Open Configuration → Web services → NinorAI integration (/admin/config/services/ninorai).

OAuth2 (recommended)

  1. First click Create user and generate password to create the ninorai service user (the OAuth client acts as this user, inheriting only the NinorAI service role's permissions).
  2. Configure OAuth signing keys once at Configuration → People → Simple OAuth (drush simple-oauth:generate-keys or the on-page generator).
  3. Click Generate OAuth client. Copy the Client ID and Client secret (the secret is shown only once) into NinorAI's site settings.

NinorAI exchanges the credentials at the token endpoint using the client_credentials grant:

POST /oauth/token
grant_type=client_credentials&client_id=<id>&client_secret=<secret>

and sends the returned token as Authorization: Bearer <token> on every request.

HTTP Basic Auth (fallback)

Click Create user and generate password and paste the username + password into NinorAI. Endpoints accept both auth methods (_auth: ['oauth2', 'basic_auth', 'cookie']).

Endpoints

Method Path Purpose
POST /api/ninorai/articles Create an article from JSON.
PATCH /api/ninorai/articles/{ninorai_id} Update an article by NinorAI ID.
GET /api/ninorai/articles/{ninorai_id} Read one article.
GET /api/ninorai/articles?page=N&per_page=M Paginated list (Site Sync read-back).
POST /api/ninorai/articles/bundle Upload a whole bundle .zip ("just the zip").

The bundle endpoint accepts either multipart/form-data with a bundle file part, or a raw application/zip request body.

Manual import

Content → Import NinorAI bundle (/admin/content/ninorai/import) — upload a .zip and a draft article is created with its images sideloaded into the managed file store and the body rewritten to use them, so the article is self-contained.

The bundle format

A NinorAI bundle is the artefact the platform builds at GET /api/v1/articles/{id}/bundle. It is a .zip containing:

  • manifest.jsontitle, slug, meta_title, meta_description, focus_keyword, language, categories[], featured_image (relative path), featured_image_alt, content_file, and schema (JSON-LD).
  • article.html — the post body, with images referenced as relative images/... paths.
  • images/ — the featured and inline image files.

Entries are read straight out of the archive and every name is basenamed, so a crafted bundle cannot traverse outside its intended targets (zip-slip safe). Imports are deduplicated by ninorai_id (the slug) — re-importing the same bundle updates the existing node (as a new revision) instead of creating a duplicate. A new import lands as a draft; an update never changes the publish state, so an article an editor has already taken live stays live.

Stored JSON-LD schema

When a ninorai is viewed in full, its stored schema is emitted into <head> as <script type="application/ld+json">. The JSON is re-encoded with JSON_HEX_TAG so it is safe to print inside a script tag.

Tests

# From the Drupal root:
vendor/bin/phpunit -c core web/modules/custom/ninorai/tests

tests/src/Unit/Bundle/BundleArchiveTest.php covers bundle parsing and the zip-slip protection with no Drupal bootstrap. Integration tests for the importer and endpoints run inside a Drupal install.