Skip to content
KismetKismetDevelopers
Beta accessllms.txt

Import and stage articles

View .md

Import editorial articles into Kismet while keeping your site’s layout and production content unchanged. Your server renders the Article body, images, Markdown and BlogPosting data through the existing Article reads.

This early-access operation family supports staging publication only. It does not activate production routes, move existing URLs, crawl a website, download media, or publish production content. Confirm access to the Article import extension before integrating.

Use @kismet-tech/sdk/server with a restricted server key. Planning and applying require content.write; reading a saved revision requires content.drafts.read; publishing requires content.publish. Staging Article list/detail reads require articles.read. Publishable browser keys cannot import or read staging Articles. Authorize editor access in your application before forwarding a request.

Through authenticated Developer MCP, use plan_article_import, apply_article_import, get_article_import_revision and publish_article_staging_revision. Supply collection_slug and input, plus id for revision operations. Collection ADMIN or MEMBER can import, scoped collection roles can review, and only ADMIN can publish. Guest login does not confer editing permission.

Staging content is independent of booking TEST/LIVE. A staging publication does not enable bookings, payments, or production serving.

import { createKismetClient } from '@kismet-tech/sdk/server';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
// Hash the source file you captured and used to prepare the Article below.
const capturedSource = await readFile('./captured-city-guide.html');
const capturedSourceSha256 = createHash('sha256').update(capturedSource).digest('hex');
const kismet = createKismetClient({
baseUrl: 'https://api.ksmt.app/v1',
apiKey: process.env.KISMET_SERVER_KEY!,
collection: 'example-stays',
});
const plan = await kismet.articleImports.plan({
key: 'source-city-guide',
expectedRevision: null, // null only for a new source key
document: {
slug: 'city-guide',
title: 'A guide to the city',
sourcePublishedAt: '2022-07-25T00:00:00.000Z',
body: [{ type: 'prose', html: '<p>Start with a walk along the waterfront.</p>' }],
hero: { url: 'https://media.example.com/waterfront.jpg', alt: 'Waterfront walking path' },
source: {
kind: 'imported',
url: 'https://www.example.com/blog/city-guide',
capturedAt: new Date().toISOString(),
sha256: capturedSourceSha256, // SHA-256 of the captured source, not an invented value
},
},
});
// Review plan.input: prose is sanitized by the server.
const revision = await kismet.articleImports.apply({
...plan.input,
planDigest: plan.planDigest,
idempotencyKey: 'city-guide-import-1',
});
const reviewed = await kismet.articleImports.getRevision(revision.id);
// Display/review reviewed.document before the separate publication action.
await kismet.articleImports.publishStaging(reviewed.id, {
channel: 'staging',
expectedPublishedRevisionId: null, // or the revision currently published to staging
});
const article = await kismet.article.get('city-guide', { channel: 'staging' });
const markdown = await kismet.article.md('city-guide', {}, { channel: 'staging' });

The equivalent REST operations are POST /v1/developer/collections/{collection}/articles/imports/plan, POST .../imports/apply, GET .../imports/revisions/{id}, and POST .../imports/revisions/{id}/publish. They accept the same JSON inputs as the SDK. Send Authorization: Bearer <server-key> and Content-Type: application/json for writes.

Apply creates or updates a canonical DRAFT Article and an immutable revision. An unpublished draft is absent from both production and staging reads. Publishing selects exactly the revision ID reviewed, even if a newer draft exists. Subsequent imports do not change that publication.

Use article.list({ channel: 'staging', limit: 20, cursor }) for your staging index. Follow page.nextCursor until null; category filtering and meta.availableCategories remain collection-scoped. Detail returns the selected revision’s full supported body, hero/media and derived schemaOrg BlogPosting, with staging revision identity in publication.

Render /blog/<slug> using your own template. Set host-specific canonical URLs and JSON-LD URLs in that template. Keep the staging site access-controlled and noindexed; the API’s private/no-store and noindex headers do not configure your host. Read staging content on your server, never put the server key in a browser bundle. Omitting channel preserves existing production Article behavior.

document.sourcePublishedAt preserves the original publication date in the immutable revision. Staging reads and BlogPosting datePublished use it; publication.stagedAt records the separate staging action. This does not change production Article dates. Omit the field to preserve a known date on reimport, or pass null to clear it. The SDK’s getRevision requests date metadata by default; REST and MCP readers use includeSourceDates: true (a query parameter in REST). Older readers receive the legacy shape by default.

To copy images into the collection library first, follow Import images. Review successful owned URLs and substitute them into the Article input. Image imports never publish an Article or repair broken source URLs automatically.

  • Retain source keys and returned Article/revision IDs. On reimport, set expectedRevision to the current draft number and use a new idempotency key. Replay an identical apply request with its original key to recover the original result.
  • 409 ARTICLE_REVISION_CONFLICT or ARTICLE_PLAN_STALE means re-plan and review. ARTICLE_CHANGED means a manager changed the canonical Article outside this importer; stop and reconcile rather than overwrite.
  • ARTICLE_IDEMPOTENCY_CONFLICT rejects reuse of a key with different input. ARTICLE_PUBLICATION_CONFLICT means another staging publication won; inspect the current staging Article’s publication.revisionId before retrying.
  • Supported body blocks are prose, image, pullquote and faq. Unknown blocks, unsafe image URLs and empty sanitized prose are rejected. Image URLs must be HTTPS; referenced media is not copied. Resolve rights, captions, alt text and durable hosting before import.
  • Optional authorId and categoryIds must refer to canonical records in the same collection. A source key’s slug cannot change, and another Article or CMS page cannot be overwritten. Taxonomy/people creation, navigation, redirects and production promotion are not part of this family.
  • Management operations are limited to 30 requests per minute per installation at the HTTP boundary. Honor 429 and Retry-After; do not retry conflicts blindly.

Before a bulk import, verify one complete article: absent before publish, full HTML/media afterward, matching Markdown and BlogPosting, safe replay, and unchanged production reads. Then repeat with a second distinct content shape before importing the rest.