curl --request POST \
--url https://api.usenotra.com/v1/posts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"title": "Ship notes for week 11",
"contentType": "blog_post",
"slug": "ship-notes-week-11",
"status": "draft"
}
'import requests
url = "https://api.usenotra.com/v1/posts"
payload = {
"title": "Ship notes for week 11",
"contentType": "blog_post",
"slug": "ship-notes-week-11",
"status": "draft"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
title: 'Ship notes for week 11',
contentType: 'blog_post',
slug: 'ship-notes-week-11',
status: 'draft'
})
};
fetch('https://api.usenotra.com/v1/posts', 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://api.usenotra.com/v1/posts",
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([
'title' => 'Ship notes for week 11',
'contentType' => 'blog_post',
'slug' => 'ship-notes-week-11',
'status' => 'draft'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://api.usenotra.com/v1/posts"
payload := strings.NewReader("{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.usenotra.com/v1/posts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.usenotra.com/v1/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}"
response = http.request(request)
puts response.read_body{
"organization": {
"id": "<string>",
"slug": "<string>",
"name": "<string>",
"logo": "<string>"
},
"post": {
"id": "<string>",
"title": "<string>",
"slug": "<string>",
"content": "<string>",
"htmlUrl": "<string>",
"markdown": "<string>",
"rawHtml": "<string>",
"recommendations": "<string>",
"contentType": "changelog",
"metadata": {
"coverImage": {
"url": "https://cdn.example.com/covers/ship-notes.png",
"alt": "Screenshot of the new editor"
},
"tags": [
"editor",
"performance"
],
"excerpt": "The editor now opens twice as fast on long posts.",
"authors": [
{
"id": "auth_8f2kq1",
"slug": "jane-doe",
"name": "Jane Doe",
"title": "Head of Product",
"avatarUrl": "https://cdn.example.com/avatars/jane.png",
"url": "https://janedoe.dev",
"x": "https://x.com/janedoe",
"linkedin": "<string>",
"github": "<string>",
"youtube": "<string>",
"bluesky": "<string>",
"mastodon": "<string>",
"threads": "<string>",
"instagram": "<string>"
}
],
"featured": false,
"noindex": false,
"hidden": false,
"displayDate": "2026-10-01"
},
"status": "draft",
"createdAt": "<string>",
"updatedAt": "<string>",
"sourceMetadata": "<unknown>"
}
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"limit": 2,
"remaining": 1,
"reset": 123
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}Create a post
Creates a post directly without generation. Omit markdown to create an empty draft you fill in later through the dashboard or PATCH /v1/posts/. Slugs are only accepted for blog posts and changelogs.
curl --request POST \
--url https://api.usenotra.com/v1/posts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"title": "Ship notes for week 11",
"contentType": "blog_post",
"slug": "ship-notes-week-11",
"status": "draft"
}
'import requests
url = "https://api.usenotra.com/v1/posts"
payload = {
"title": "Ship notes for week 11",
"contentType": "blog_post",
"slug": "ship-notes-week-11",
"status": "draft"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
title: 'Ship notes for week 11',
contentType: 'blog_post',
slug: 'ship-notes-week-11',
status: 'draft'
})
};
fetch('https://api.usenotra.com/v1/posts', 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://api.usenotra.com/v1/posts",
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([
'title' => 'Ship notes for week 11',
'contentType' => 'blog_post',
'slug' => 'ship-notes-week-11',
'status' => 'draft'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://api.usenotra.com/v1/posts"
payload := strings.NewReader("{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.usenotra.com/v1/posts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.usenotra.com/v1/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"title\": \"Ship notes for week 11\",\n \"contentType\": \"blog_post\",\n \"slug\": \"ship-notes-week-11\",\n \"status\": \"draft\"\n}"
response = http.request(request)
puts response.read_body{
"organization": {
"id": "<string>",
"slug": "<string>",
"name": "<string>",
"logo": "<string>"
},
"post": {
"id": "<string>",
"title": "<string>",
"slug": "<string>",
"content": "<string>",
"htmlUrl": "<string>",
"markdown": "<string>",
"rawHtml": "<string>",
"recommendations": "<string>",
"contentType": "changelog",
"metadata": {
"coverImage": {
"url": "https://cdn.example.com/covers/ship-notes.png",
"alt": "Screenshot of the new editor"
},
"tags": [
"editor",
"performance"
],
"excerpt": "The editor now opens twice as fast on long posts.",
"authors": [
{
"id": "auth_8f2kq1",
"slug": "jane-doe",
"name": "Jane Doe",
"title": "Head of Product",
"avatarUrl": "https://cdn.example.com/avatars/jane.png",
"url": "https://janedoe.dev",
"x": "https://x.com/janedoe",
"linkedin": "<string>",
"github": "<string>",
"youtube": "<string>",
"bluesky": "<string>",
"mastodon": "<string>",
"threads": "<string>",
"instagram": "<string>"
}
],
"featured": false,
"noindex": false,
"hidden": false,
"displayDate": "2026-10-01"
},
"status": "draft",
"createdAt": "<string>",
"updatedAt": "<string>",
"sourceMetadata": "<unknown>"
}
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}{
"error": "<string>",
"limit": 2,
"remaining": 1,
"reset": 123
}{
"error": "<string>",
"code": "<string>",
"recovery": "<string>"
}Authorizations
Send your API key in the Authorization header as Bearer API_KEY. Account API keys work in every workspace you belong to: send X-Notra-Organization-Id with the workspace id or slug to choose one. Workspace endpoints require it when you belong to more than one workspace; GET /v1/me/workspaces works without it. Organization keys only accept their own workspace there.
Body
Slugs are normalized to lowercase letters, numbers, and hyphens and only accepted for blog posts and changelogs. Omit markdown to create an empty post you fill in later.
1 - 120blog_post, changelog, linkedin_post, twitter_post 1 - 160100000draft, published Was this page helpful?