---
title: "Import an existing article"
method: POST
path: "/content/import"
tags: ["Content"]
---

# Import an existing article

`POST /content/import`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/content/import`
- Auth: one of:
  - API key in header `X-API-KEY`
  - HTTP bearer

Create a Frase document from an article written elsewhere, then score it and build its optimization brief. Supply the body directly (body + body_format) or a source_url for Frase to fetch — the two are mutually exclusive. Takes one article credit per (article, billing period), so regenerating the same article later in the same period does not charge again.

**Retries are safe.** Send an Idempotency-Key header and a repeated request returns 200 with the original content_id instead of importing again. Without the header the key is derived from the request body, so an identical retry is still deduplicated. This matters because a request can be retried automatically on a 5xx, and each import would otherwise take its own credit.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `title` string, required: The article's title.
  - `body` string: The article itself. Mutually exclusive with source_url.
  - `body_format` 'markdown' | 'html': How to read body. Declared rather than sniffed: markdown containing an inline <p> would otherwise be mis-parsed, and the scorer branches on the same signal.
  - `source_url` string, uri: A page for Frase to fetch instead of sending a body. Mutually exclusive with body.
  - `target_keywords` string[]: Keywords the article targets; the first is the primary. Without these the SEO score is computed against an empty set and roughly a third of it is unreachable, so supply them if you have them.
  - `site_id` string, uuid: Site to attach the article to. Omitted, it draws on the org-wide article budget rather than a per-site one.
  - `content_type` string: e.g. blog_post. Auto-detected when omitted.
  - `target_word_count` integer
  - `competitor_urls` string[]: Competing pages to research against. Each is validated; a refusal names the offending index.

## Response `200`

Already imported. Returned when an identical request is retried — matched on the Idempotency-Key header, or on a hash of the request body when that header is absent. The same content_id comes back, no second article credit is taken, and no fetch, scoring or research work is repeated. Note there is no overage_incurred field: nothing was billed on this call.

- object
  - `data` object
    - `content_id` string, uuid
    - `brief_id` string, uuid, nullable
    - `editor_url` string: Deep link to the article in the Frase editor.
    - `creation_source` 'import': Always "import": the article was brought in by the customer, not authored by Frase. This is what governs whether the piece may ever auto-publish, so it records AUTHORSHIP, not the door the request came through. The entry surface (e.g. v1_api) is recorded separately on the import summary.
    - `target_keywords` string[]: The STORED list, echoed back. Deduplicated case-insensitively and capped at 25, so compare against what you sent to see any truncation. Index 0 is primary.
    - `warnings` string[]: Non-fatal advisories. Present and non-empty when the article was imported without target_keywords: keyword coverage and placement are 35 of the SEO score's 100 points, so the score is capped until keywords are set and the article is rescored.
    - `replayed` true: Always true on a 200: this request matched an earlier import and nothing was created.

## Other responses

- `201`: Imported.
- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `409`: Idempotency-Key conflict, in one of two shapes. `idempotency_key_reuse` — the key was already used for a DIFFERENT request body; returned instead of replaying the earlier article, which would report someone else's import as this caller's success. Use a new key. `import_in_progress` — an import with this key is still running; the response carries Retry-After, and a retry after that delay returns the finished article with a 200. Concurrent retries are serialised by a durable claim, so only one of them performs the fetch, scoring and brief creation.
- `413`: The body is larger than the import limit.
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
