REST API Quick Start
Five steps: verify your key, pick a brand, pick a collection, create a draft, confirm it landed. Nothing here publishes, queues, or schedules anything.
Before you start
Section titled “Before you start”You need:
- A Nuelink account with at least one brand and one collection.
- An API key from Settings → API.
curl, or any server-side HTTP client.
Put the key in an environment variable. Not in source code, not in browser JavaScript, not in a URL, not in a log:
read -rs NUELINK_API_KEY && export NUELINK_API_KEYexport NUELINK_API_BASE="https://app.nuelink.com/api/public/v1"read -rs does not echo what you type, so the key never appears on screen or as a literal in your history. In CI, inject NUELINK_API_KEY from your secret manager instead.
# confirm it is set without printing it[ -n "$NUELINK_API_KEY" ] && echo "key loaded"1. Verify the key
Section titled “1. Verify the key”curl --fail-with-body --silent --show-error \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ "$NUELINK_API_BASE/me"const base = process.env.NUELINK_API_BASE;const apiKey = process.env.NUELINK_API_KEY;
const response = await fetch(`${base}/me`, { headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', },});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$apiKey = getenv('NUELINK_API_KEY');
$ch = curl_init("{$base}/me");
curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;{ "status": "success", "data": { "id": "user_id", "name": "Example User", "joinedAt": "2026-01-01T00:00:00.000000Z", "timezone": "UTC" }}GET /auth returns the same response, if you’d rather your code say “validate” than “who am I”.
2. Pick a brand
Section titled “2. Pick a brand”curl --fail-with-body --silent --show-error \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ "$NUELINK_API_BASE/brands?page=1&per_page=100"const base = process.env.NUELINK_API_BASE;const apiKey = process.env.NUELINK_API_KEY;
const response = await fetch(`${base}/brands?page=1&per_page=100`, { headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', },});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$apiKey = getenv('NUELINK_API_KEY');
$ch = curl_init("{$base}/brands?page=1&per_page=100");
curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;Save the id of the brand you want:
export BRAND_ID="1001"3. Pick a collection
Section titled “3. Pick a collection”curl --fail-with-body --silent --show-error \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ "$NUELINK_API_BASE/brands/$BRAND_ID/collections?page=1&per_page=100"const base = process.env.NUELINK_API_BASE;const brandId = process.env.BRAND_ID;const apiKey = process.env.NUELINK_API_KEY;
const response = await fetch(`${base}/brands/${brandId}/collections?page=1&per_page=100`, { headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', },});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$brandId = getenv('BRAND_ID');$apiKey = getenv('NUELINK_API_KEY');
$ch = curl_init("{$base}/brands/{$brandId}/collections?page=1&per_page=100");
curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;export COLLECTION_ID="2001"Look at each collection’s channels before you choose. The collection decides where a non-draft post publishes. If you want a target that can’t reach anyone while you’re testing, create a collection with no channels, see Endpoints.
4. Create a draft
Section titled “4. Create a draft”curl --fail-with-body --silent --show-error \ -X POST \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ "$NUELINK_API_BASE/brands/$BRAND_ID/collections/$COLLECTION_ID/posts" \ --data '{ "title": "API quick-start draft", "caption": "This draft was created through the Nuelink API.", "publishMode": "DRAFT" }'const base = process.env.NUELINK_API_BASE;const brandId = process.env.BRAND_ID;const collectionId = process.env.COLLECTION_ID;const apiKey = process.env.NUELINK_API_KEY;
const payload = { title: 'API quick-start draft', caption: 'This draft was created through the Nuelink API.', publishMode: 'DRAFT',};
const response = await fetch(`${base}/brands/${brandId}/collections/${collectionId}/posts`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(payload),});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$brandId = getenv('BRAND_ID');$collectionId = getenv('COLLECTION_ID');$apiKey = getenv('NUELINK_API_KEY');
$payload = [ 'title' => 'API quick-start draft', 'caption' => 'This draft was created through the Nuelink API.', 'publishMode' => 'DRAFT',];
$ch = curl_init("{$base}/brands/{$brandId}/collections/{$collectionId}/posts");
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', 'Content-Type: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;{ "status": "success", "data": { "id": 4733837, "message": "Post created successfully" }}5. Confirm it landed
Section titled “5. Confirm it landed”The create response tells you a post exists. It doesn’t tell you what kind. Read it back:
curl --fail-with-body --silent --show-error \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ "$NUELINK_API_BASE/brands/$BRAND_ID/collections/$COLLECTION_ID/posts?page=1&per_page=100"const base = process.env.NUELINK_API_BASE;const brandId = process.env.BRAND_ID;const collectionId = process.env.COLLECTION_ID;const apiKey = process.env.NUELINK_API_KEY;
const response = await fetch(`${base}/brands/${brandId}/collections/${collectionId}/posts?page=1&per_page=100`, { headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', },});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$brandId = getenv('BRAND_ID');$collectionId = getenv('COLLECTION_ID');$apiKey = getenv('NUELINK_API_KEY');
$ch = curl_init("{$base}/brands/{$brandId}/collections/{$collectionId}/posts?page=1&per_page=100");
curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;Find your post ID and check:
{ "postingType": "DRAFT", "status": "DRAFT", "postDate": null}If postDate isn’t null, something scheduled it. Fix that before you write any more code.
Optional: attach media
Section titled “Optional: attach media”The multipart field name is media:
curl --fail-with-body --silent --show-error \ -X POST \ -H "Authorization: Bearer $NUELINK_API_KEY" \ -H "Accept: application/json" \ "$NUELINK_API_BASE/brands/$BRAND_ID/media" \ -F "media=@./launch.jpg"import { readFile } from 'node:fs/promises';
const base = process.env.NUELINK_API_BASE;const brandId = process.env.BRAND_ID;const apiKey = process.env.NUELINK_API_KEY;
const formData = new FormData();formData.append('media', new Blob([await readFile('./launch.jpg')]), 'launch.jpg');
const response = await fetch(`${base}/brands/${brandId}/media`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json', }, body: formData,});
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const data = await response.json();console.log(data);<?php
$base = getenv('NUELINK_API_BASE');$brandId = getenv('BRAND_ID');$apiKey = getenv('NUELINK_API_KEY');
$ch = curl_init("{$base}/brands/{$brandId}/media");
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'media' => new CURLFile('./launch.jpg'), ], CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true,]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP {$status}: {$response}");}
echo $response;Pass the returned string id into the post’s media array as {"id": "..."}. GET /media also returns opaque string IDs that can be used in post creation. If the file is already at a public URL, skip the upload and pass {"url": "..."} instead. See Endpoints.
Before you go to production
Section titled “Before you go to production”- Resolve brand and collection IDs at runtime. Never hardcode an ID from an example.
- Default uncertain publishing intent to
DRAFT. - Treat
QUEUE,SCHEDULE, andIMMEDIATEas explicit decisions a human made. - Don’t auto-retry a create after a timeout or a
5xx. See Errors. - Respect the rate-limit headers and the brand’s timezone.
- Keep the key in a server-side secret manager.
Next: Authentication, Errors, Endpoints.