# Import images

> Review remote images, copy them into a collection media library, and inspect each result before using it in content.


Import images you are authorized to copy into your collection's media library. This early-access operation family requires a matching API deployment and SDK. It does not publish content, activate routes, or change a site's credentials.

## Use your coding agent

With manager OAuth, preview `import_collection_images` using `collection_slug` and `input` containing a fresh UUID `requestId` and up to 50 unique `urls` (`src` and optional `alt`). Review the normalized plan, then confirm the same input with `confirm: true` and its exact `planDigest`. Collection ADMIN or MEMBER may import; scoped collection roles may inspect `get_collection_media_import`. Guest login is not editor authority.

Poll the returned job ID. Use `resume_collection_media_import` only to recover unfinished work: preview, review, then confirm. An active worker cannot be resumed concurrently. This workflow uses manager OAuth and does not require issuing a site server key.

Imports run asynchronously and may wait in a queue. A pending job is not evidence that downloading started. Interrupted processing resumes from settled source results; failed jobs wait for an explicit authorized resume. Inspect the returned status rather than assuming an accepted request has finished.

## Use your server

Use a restricted server secret with collection-wide `media.import` for plan/create/resume and independent `media.read` for progress. The methods are absent from the browser client.

```ts
import { createKismetClient } from '@kismet-tech/sdk/server';
import { randomUUID } from 'node:crypto';

const kismet = createKismetClient({ collection: 'example-stays',
  apiKey: process.env.KISMET_SERVER_KEY!, baseUrl: 'https://api.ksmt.app/v1' });
const plan = await kismet.mediaImports.plan({ requestId: randomUUID(),
  urls: [{ src: 'https://media.example.com/waterfront.jpg', alt: 'Waterfront path' }] });
// Show plan.input and warnings to the authorized editor before confirming.
const job = await kismet.mediaImports.create({ ...plan.input,
  planDigest: plan.planDigest, confirm: true });
const progress = await kismet.mediaImports.get(job.id);
// Poll with backoff; completed can include failed sources.
for (const result of progress.results) {
  if (result.status !== 'failed' && result.image) console.log(result.image.id, result.image.url);
}
```

REST uses `POST /v1/developer/collections/{collection}/media/imports/plan`, `POST .../imports`, `GET .../imports/{id}`, and `POST .../imports/{id}/resume` (`{ "confirm": true }`). Send the server credential in the Authorization header, never in a URL.

## Convert a static SVG before importing

The shared importer does not store or serve SVG. For self-contained vector logos
and illustrations, the SDK source/evaluation package includes `media-converter`,
a separate server-side converter. It accepts bounded static geometry and simple
local CSS/clipping, then emits PNG only. Scripts, external resources, entities,
animation, embedded HTML, unsupported constructs, and cyclic references are
rejected; nothing is silently removed except the supported CSS after applying it.

Build the converter from the supplied SDK package (Docker is required):

```sh
docker build -t kismet-media-converter ./node_modules/@kismet-tech/sdk/media-converter
```

Run it on one source file you are authorized to copy. Use an empty private output
directory, absolute paths, and the full isolation profile:

```sh
docker run --rm --user "$(id -u):$(id -g)" \
  --network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges \
  --pids-limit=64 --memory=256m --cpus=1 \
  --tmpfs /tmp:rw,noexec,nosuid,size=16m \
  --mount type=bind,source=/absolute/source.svg,target=/input.svg,readonly \
  --mount type=bind,source=/absolute/empty-output,target=/output \
  kismet-media-converter
```

Never pass production credentials, extra environment variables, or other private
mounts to this container. The converter checks its runtime limits and refuses
host, root, networked, writable-root, or unbounded execution. It caps source bytes
at 512 KiB, normalized geometry at 2 MiB/4,096 nodes/32 levels, output sides at
2,048 pixels (up to 4× intrinsic size), and
rendering at five seconds inside a 20-second process deadline. It does not
overwrite an existing `output.png`. Retain its source/PNG SHA-256 receipt privately.

There is no local-file upload or arbitrary SVG-storage operation in this API.
Serve the resulting PNG from your approved source host and use its HTTPS URL in
the normal reviewed media-import request. Inspect the managed-library result
before replacing a content binding. Keep the original source unchanged; if a
vector uses unsupported features, obtain a reviewed raster export rather than
relaxing validation. Retain the unchanged original, conversion receipt, PNG,
import request UUID, returned image ID, and owned URL in a private durable audit
record. The receipt links the original SVG checksum to the exact PNG checksum;
it is not a server attestation of third-party rights. Keep it after the import
job expires—Kismet does not retain the original SVG or a permanent conversion
receipt in that job. Converting an image does not publish a page.

## Recovery and safety

- Planning writes nothing and is not a reachability or rights check. Creation requires the exact reviewed digest. Replay the identical request UUID to recover its job; changed input with that UUID is rejected.
- Each source yields `imported`, `existing`, or `failed`. Only successful owned image URLs belong in subsequent Article drafts. A completed job is not proof every image imported. Retry failed sources in a separately reviewed new request.
- Progress persists in the shared cache for 24 hours, including settled source results. Explicit resume skips those results. It is not a permanent receipt or a guarantee against cache data loss; retain your reviewed source-to-owned-URL mapping privately.
- Four new jobs per collection per continuously refilled hour, at most 50 sources per job. Polling does not consume this job budget. Honor `429` and back off. Unavailable persistence refuses creation before image writes.
- Only public HTTPS JPEG, PNG, WebP, or GIF sources are accepted. Every DNS lookup and redirect is checked against private networks; credentials are not forwarded. Downloads are bounded to 10 MiB and decoded images to 40 million pixels. SVG, mismatched bytes, broken sources, and unsafe redirects fail independently.
- Source URL query strings can contain private access tokens. Do not put source URLs in public logs, browser state, or chat. The library preserves collection ownership and deduplicates image content; it does not grant rights to copy third-party media.

After reviewing the results, replace the appropriate hero/body URLs in an Article import plan, review the new immutable revision, and publish to staging only as a separate authorized action. Production publication is not part of this workflow.
