curl --request POST \
--url https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles \
--header 'API-KEY: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"insert_external_links": true,
"insert_internal_links": true,
"manual_outline": true
}
'import requests
url = "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles"
payload = {
"insert_external_links": True,
"insert_internal_links": True,
"manual_outline": True
}
headers = {
"API-KEY": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'API-KEY': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({insert_external_links: true, insert_internal_links: true, manual_outline: true})
};
fetch('https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'insert_external_links' => true,
'insert_internal_links' => true,
'manual_outline' => true
]),
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles"
payload := strings.NewReader("{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("API-KEY", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles")
.header("API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["API-KEY"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}"
response = http.request(request)
puts response.read_body{
"inserted_at": "2024-04-12T15:23:45Z",
"id": 123,
"manual_outline": true,
"state": "waiting_for_user_input",
"updated_at": "2024-06-01T09:17:33Z"
}{
"error": {
"details": [
{
"field": "keywords",
"reason": "required"
}
],
"reason": "validation_error"
}
}{
"error": {
"details": [],
"reason": "unauthorized"
}
}{
"error": {
"details": [],
"reason": "permission_denied"
}
}{
"error": {
"details": [],
"reason": "not_found"
}
}{
"error": {
"details": [
{
"allowed": [
"application/json"
],
"field": "accept",
"reason": "unsupported"
}
],
"message": "Accept must be one of: application/json",
"reason": "not_acceptable"
}
}{
"error": {
"details": [],
"message": "The request conflicts with the current resource state.",
"reason": "conflict"
}
}{
"error": {
"details": [],
"message": "Set Content-Type: application/json.",
"reason": "unsupported_media_type"
}
}{
"error": {
"details": [],
"message": "The request was understood but could not be processed.",
"reason": "unprocessable_entity"
}
}{
"error": {
"details": [
{
"retry_after": 30
}
],
"reason": "rate_limit_exceeded"
}
}Create AI Article
Triggers AI article generation for a Content Editor. Template, voice, custom instructions, target word count etc. are inherited from the parent Content Editor.
The Content Editor must be in completed state; otherwise the request answers 409. If the SEO or
AI Search guidelines are being recalculated when the request lands, the article stays in new state
until they settle, then transitions to generating_outline. If the guidelines never complete (failure
or timeout), the article transitions to failed.
The operation is asynchronous — the response returns immediately with the article in
new state.
States: new → generating_outline → waiting_for_user_input (only if manual_outline: true) → writing → completed / failed.
Webhooks: content_editor.ai_article.waiting_for_user_input, content_editor.ai_article.completed,
content_editor.ai_article.failed. As a fallback, poll GET .../ai_articles/:article_id for state updates.
Cost: Enterprise and some older plans consume an AI Article credit per successful request; other plans include AI Articles in the Content Editor.
Rate limit: 10 requests/sec
curl --request POST \
--url https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles \
--header 'API-KEY: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"insert_external_links": true,
"insert_internal_links": true,
"manual_outline": true
}
'import requests
url = "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles"
payload = {
"insert_external_links": True,
"insert_internal_links": True,
"manual_outline": True
}
headers = {
"API-KEY": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'API-KEY': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({insert_external_links: true, insert_internal_links: true, manual_outline: true})
};
fetch('https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'insert_external_links' => true,
'insert_internal_links' => true,
'manual_outline' => true
]),
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles"
payload := strings.NewReader("{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("API-KEY", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles")
.header("API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.surferseo.com/api/v2/workspaces/{workspace_id}/content_editors/{content_editor_id}/ai_articles")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["API-KEY"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"insert_external_links\": true,\n \"insert_internal_links\": true,\n \"manual_outline\": true\n}"
response = http.request(request)
puts response.read_body{
"inserted_at": "2024-04-12T15:23:45Z",
"id": 123,
"manual_outline": true,
"state": "waiting_for_user_input",
"updated_at": "2024-06-01T09:17:33Z"
}{
"error": {
"details": [
{
"field": "keywords",
"reason": "required"
}
],
"reason": "validation_error"
}
}{
"error": {
"details": [],
"reason": "unauthorized"
}
}{
"error": {
"details": [],
"reason": "permission_denied"
}
}{
"error": {
"details": [],
"reason": "not_found"
}
}{
"error": {
"details": [
{
"allowed": [
"application/json"
],
"field": "accept",
"reason": "unsupported"
}
],
"message": "Accept must be one of: application/json",
"reason": "not_acceptable"
}
}{
"error": {
"details": [],
"message": "The request conflicts with the current resource state.",
"reason": "conflict"
}
}{
"error": {
"details": [],
"message": "Set Content-Type: application/json.",
"reason": "unsupported_media_type"
}
}{
"error": {
"details": [],
"message": "The request was understood but could not be processed.",
"reason": "unprocessable_entity"
}
}{
"error": {
"details": [
{
"retry_after": 30
}
],
"reason": "rate_limit_exceeded"
}
}Authorizations
Path Parameters
x >= 1123
x >= 1123
Body
Create AI article params
If true, the article is written with links to relevant third-party sources.
true
If true, the article is written with links to other pages on the brand's site. Requires use_brand_knowledge on the Content Editor - no internal links are inserted without the flag.
true
If true, the article generation pauses at waiting_for_user_input so the outline can be edited before writing continues.
true
Response
Created AI article
"2024-04-12T15:23:45Z"
x >= 1123
true
new, generating_outline, waiting_for_user_input, writing, completed, failed "waiting_for_user_input"
"2024-06-01T09:17:33Z"
Was this page helpful?