Example Guide
Nuelink API: Alpha Documentation
Section titled “Nuelink API: Alpha Documentation”Version: 1.0 (Alpha) Base URL: https://nuelink.com/api/public/v1
⚠️ Alpha notice: This is an early alpha release. Endpoints and response shapes may change before general availability. Breaking changes will be announced via email to registered developers.
The Nuelink API lets you programmatically publish content to every social channel Nuelink supports (Facebook, Instagram, Threads, LinkedIn, Google Business, YouTube, TikTok, Pinterest, X (Twitter), Mastodon, Bluesky, and Telegram) from your own applications.
This first release focuses on a single workflow: creating posts and uploading media. Listing, updating, and deleting existing posts will arrive in a later release.
Table of contents
Section titled “Table of contents”- How Nuelink is organized
- Quickstart: your first post in 5 steps
- Authentication
- Rate limits
- Errors
- Pagination
- Endpoints
- Platform-specific options
- Changelog
How Nuelink is organized
Section titled “How Nuelink is organized”Before you make your first call, it helps to understand the three-level hierarchy Nuelink uses:
Account
└── Brand (workspace, e.g. “Fernwood Botanicals”)
├── Channels (the connected social accounts: IG, X, TikTok, …)
└── Collections (content folders grouped by theme or campaign)
└── Posts (social media posts)
- Brand: A fully-isolated workspace. Each brand has its own channels, collections, automations, and insights, so you can manage multiple companies or projects without their data mixing. Most accounts have one brand; agencies and power users typically have several.
- Collection: A content folder inside a brand, typically organized by theme or campaign (e.g. “Summer Campaign”, “Product Launches”, “Customer Stories”). Each collection has a set of default channels and weekly time slots (aka Queue). When you create a post inside a collection, it publishes to the collection’s default channels.
- Channel: A single connected social account, e.g. @fernwoodbotanicals on Instagram or your company LinkedIn page. Channels belong to a brand.
So creating a post is always:
Pick a brand → pick a collection inside it → POST the post to that collection.
Channels are listed as a separate endpoint so you can inspect what’s connected to the brand, but you don’t pass channel IDs when creating a post, the collection’s configuration determines which channels receive it.
Quickstart: your first post in 5 steps
Section titled “Quickstart: your first post in 5 steps”Each step below maps to an endpoint documented further down. All examples use curl; adapt as needed for your language.
1. Verify your token works
Section titled “1. Verify your token works”curl https://nuelink.com/api/public/v1/me \
-H “Authorization: Bearer YOUR_API_KEY”
2. List your brands and pick one
Section titled “2. List your brands and pick one”curl https://nuelink.com/api/public/v1/brands \
-H “Authorization: Bearer YOUR_API_KEY”
Grab the id of the brand you want to post from. We’ll call it BRAND_ID.
3. List collections in that brand
Section titled “3. List collections in that brand”curl “https://nuelink.com/api/public/v1/brands/BRAND_ID/collections” \
-H “Authorization: Bearer YOUR_API_KEY”
Pick the collection whose channels you want to publish to. We’ll call it COLLECTION_ID.
4. (Optional) Upload a media file
Section titled “4. (Optional) Upload a media file”Skip this step if your post is text-only.
curl -X POST https://nuelink.com/api/public/v1/brands/BRAND_ID/media \
-H “Authorization: Bearer YOUR_API_KEY” \
-F “media=@./photo.jpg”
Save the returned id.
5. Create the post
Section titled “5. Create the post”curl -X POST https://nuelink.com/api/public/v1/brands/BRAND_ID/collections/COLLECTION_ID/posts \
-H “Authorization: Bearer YOUR_API_KEY” \
-H “Content-Type: application/json” \
-d ’{
“caption”: “Hello from the Nuelink API 👋”,
“publishMode”: “IMMEDIATE”,
“media”: [{ “id”: “MEDIA_ID_FROM_STEP_4” }]
}’
You’ll get back a post ID and the post will be pushed to every channel in the collection.
Authentication
Section titled “Authentication”All requests require a bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
You can generate an API key from your Nuelink dashboard under Settings → API.
Tokens are long-lived and do not need to be refreshed. If a token is leaked, revoke it from the same dashboard screen and generate a new one.
Missing or invalid token
Section titled “Missing or invalid token”Request:
curl https://nuelink.com/api/public/v1/me
Response: 401 Unauthorized:
{
“status”: “error”,
“message”: “Token is required”,
“errors”: {
“authorization”: “Token is required”
}
}
Other 401 variants you may see:
{
“status”: “error”,
“message”: “Token is invalid”,
“errors”: {
“authorization”: “Token is invalid”
}
}
{
“status”: “error”,
“message”: “Token has been revoked”,
“errors”: {
“authorization”: “Token has been revoked”
}
}
Rate limits
Section titled “Rate limits”Every response includes three headers showing your current rate limit status:
| Header | Meaning |
|---|---|
| x-ratelimit-limit | Maximum requests allowed in the current window. |
| --- | --- |
| x-ratelimit-remaining | Requests remaining in the current window. |
| --- | --- |
| x-ratelimit-reset | Seconds until the window resets. |
| --- | --- |
Currently the limit is 30 requests per minute per API key across all endpoints.
When you exceed the limit you’ll receive 429 Too Many Requests. Back off until x-ratelimit-reset elapses, then retry.
Response: 429 Too Many Requests:
{
“status”: “error”,
“message”: “Too many requests. Please try again later.”,
“errors”: {
“rate_limit”: “Too many requests. Please try again later.”
}
}
Errors
Section titled “Errors”Nuelink uses standard HTTP status codes.
All error responses, regardless of status code, share the same envelope:
{
“status”: “error”,
“message”: “Human-readable description of what went wrong.”,
“errors”: {
“fieldName”: ”…”
}
}
The errors object is always present and always maps field names to messages. Two shapes are possible depending on the error type:
Most errors (401, 403, 404, 429, 500) use a single string per field:
“errors”: {
“authorization”: “Token is required”
}
Validation errors (422) follow Laravel’s default and return an array of strings per field, because a single field can fail multiple rules at once:
“errors”: {
“caption”: [
“The caption field is required when poll is not present.”
],
“scheduledAt”: [
“The scheduled at field is required when publish mode is SCHEDULE.”,
“The scheduled at must be a future date.”
]
}
On validation failures the key is the request field that failed. On authentication failures the key is authorization. On resource-not-found errors the key identifies the missing resource (e.g. brand, collection).
Status code reference
Section titled “Status code reference”| Code | Meaning | Typical cause |
|---|---|---|
| 200 | OK | Successful GET. |
| --- | --- | --- |
| 201 | Created | Successful POST. |
| --- | --- | --- |
| 401 | Unauthorized | Missing, malformed, or revoked API key. |
| --- | --- | --- |
| 403 | Forbidden | Token is valid but doesn’t have access to the requested brand or collection. |
| --- | --- | --- |
| 404 | Not Found | Brand, collection, channel, or media ID doesn’t exist. |
| --- | --- | --- |
| 422 | Unprocessable Entity | Request body failed validation, see errors. |
| --- | --- | --- |
| 429 | Too Many Requests | Rate limit exceeded. |
| --- | --- | --- |
| 500 | Internal Server Error | Something broke on our end. Safe to retry with exponential backoff. |
| --- | --- | --- |
Validation error example
Section titled “Validation error example”When the caption field is missing and no poll is provided:
Response: 422 Unprocessable Entity:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“caption”: [
“The caption field is required when poll is not present.”
]
}
}
Resource-not-found example
Section titled “Resource-not-found example”When a channel referenced inside a YouTube playlist doesn’t belong to the brand:
Response: 404 Not Found:
{
“status”: “error”,
“message”: “Channel not found or does not belong to the brand”,
“errors”: {
“platforms.youtube.playlists.0.channelId”: “Channel not found or does not belong to the brand”
}
}
Pagination
Section titled “Pagination”List endpoints (/brands, /collections, /channels) return paginated results. Control pagination with two query parameters:
| Parameter | Type | Default | Min | Max | Description |
|---|---|---|---|---|---|
| page | integer | 1 | 1 | - | Page number to fetch. |
| --- | --- | --- | --- | --- | --- |
| per_page | integer | 10 | 1 | 100 | Items per page. |
| --- | --- | --- | --- | --- | --- |
Requesting per_page outside the 1-100 range returns a 422 Unprocessable Entity with a validation error.
Every paginated response includes a pagination object:
{
“pagination”: {
“currentPage”: 1,
“perPage”: 10,
“total”: 8,
“lastPage”: 1,
“nextPageUrl”: null,
“prevPageUrl”: null
}
}
- total is the total number of items across all pages.
- nextPageUrl and prevPageUrl are fully-formed URLs you can call directly, or null when there is no next/previous page.
Endpoints
Section titled “Endpoints”GET /me
Section titled “GET /me”Returns the authenticated user’s profile. Useful as a health check to confirm a token works.
Request:
curl https://nuelink.com/api/public/v1/me \
-H “Authorization: Bearer YOUR_API_KEY”
Response: 200 OK:
{
“status”: “success”,
“data”: {
“id”: “7860a8ed7591c8eff18659a55b3e80e8”,
“name”: “Maya Holloway”,
“joinedAt”: “2022-05-23T23:55:51.000000Z”,
“timezone”: “Europe/London”
}
}
Response fields:
| Field | Type | Description |
|---|---|---|
| id | string | Opaque user identifier. |
| name | string | Display name on the Nuelink account. |
| joinedAt | string | ISO 8601 timestamp of account creation. |
| timezone | string | IANA timezone name (e.g. Europe/London). |
GET /brands
Section titled “GET /brands”Lists all brands the authenticated user has access to.
Query parameters:
| Parameter | Type | Default | Min | Max | Description |
|---|---|---|---|---|---|
| page | integer | 1 | 1 | - | Page number. |
| per_page | integer | 10 | 1 | 100 | Items per page. |
Request:
curl “https://nuelink.com/api/public/v1/brands?per_page=10&page=1” \
-H “Authorization: Bearer YOUR_API_KEY”
Response: 200 OK:
{
“status”: “success”,
“data”: [
{
“id”: 13493,
“title”: “Fernwood Botanicals”,
“description”: “Main workspace for Fernwood Botanicals social channels”,
“timezone”: “America/New_York”,
“createdAt”: “2023-07-20T13:05:31.000000Z”,
“updatedAt”: “2026-04-15T13:45:23.000000Z”
},
{
“id”: 9682,
“title”: “Harper & Vine Studio”,
“description”: “Photography studio side project”,
“timezone”: “America/New_York”,
“createdAt”: “2023-05-16T10:42:28.000000Z”,
“updatedAt”: “2026-03-30T01:07:43.000000Z”
}
],
“pagination”: {
“currentPage”: 1,
“perPage”: 10,
“total”: 2,
“lastPage”: 1,
“nextPageUrl”: null,
“prevPageUrl”: null
}
}
Response fields (per item):
| Field | Type | Description |
|---|---|---|
| id | integer | Brand identifier. Use this as {brandId} in URLs. |
| --- | --- | --- |
| title | string | Display name of the brand. |
| --- | --- | --- |
| description | string | null | Optional free-text description. |
| --- | --- | --- |
| timezone | string | IANA timezone used by this brand for scheduling. |
| --- | --- | --- |
| createdAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
| updatedAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
GET /brands/{brandId}/collections
Section titled “GET /brands/{brandId}/collections”Lists all collections belonging to a brand, including a summary of the channels attached to each.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
| brandId | integer | ID from the brands list. |
| --- | --- | --- |
Query parameters:
| Parameter | Type | Default | Min | Max | Description |
|---|---|---|---|---|---|
| page | integer | 1 | 1 | - | Page number. |
| per_page | integer | 10 | 1 | 100 | Items per page. |
Request:
curl “https://nuelink.com/api/public/v1/brands/13493/collections?page=1&per_page=10” \
-H “Authorization: Bearer YOUR_API_KEY”
Response: 200 OK:
{
“status”: “success”,
“data”: [
{
“id”: 98843,
“title”: “Product Launches”,
“description”: “Announcements for new skincare drops and collections”,
“status”: “ACTIVE”,
“createdAt”: “2026-04-08T09:46:21.000000Z”,
“updatedAt”: “2026-04-08T09:46:21.000000Z”,
“channels”: []
},
{
“id”: 40656,
“title”: “Summer Garden Campaign”,
“description”: “Seasonal content for the Summer Garden line”,
“status”: “ACTIVE”,
“createdAt”: “2023-09-19T13:45:26.000000Z”,
“updatedAt”: “2025-02-23T10:11:16.000000Z”,
“channels”: [
{
“id”: 60111,
“name”: “FernwoodBotanicals”,
“type”: “X”,
“status”: “ACTIVE”,
“createdAt”: “2023-08-10T11:38:53.000000Z”,
“updatedAt”: “2026-01-06T10:55:07.000000Z”
},
{
“id”: 60112,
“name”: “fernwoodbotanicals”,
“type”: “Instagram”,
“status”: “INACTIVE”,
“createdAt”: “2023-08-10T11:39:10.000000Z”,
“updatedAt”: “2025-04-06T11:00:17.000000Z”
}
]
},
{
“id”: 33273,
“title”: “Behind the Scenes”,
“description”: “Studio shots and team spotlights”,
“status”: “ACTIVE”,
“createdAt”: “2023-07-20T13:05:31.000000Z”,
“updatedAt”: “2025-02-22T21:20:36.000000Z”,
“channels”: []
}
],
“pagination”: {
“currentPage”: 1,
“perPage”: 10,
“total”: 3,
“lastPage”: 1,
“nextPageUrl”: null,
“prevPageUrl”: null
}
}
Response fields (per collection):
| Field | Type | Description |
|---|---|---|
| id | integer | Collection identifier. Use as {collectionId} in URLs. |
| --- | --- | --- |
| title | string | Display name. |
| --- | --- | --- |
| description | string | null | Optional free-text description. |
| --- | --- | --- |
| status | string | Current state of the collection (e.g. ACTIVE). |
| --- | --- | --- |
| createdAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
| updatedAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
| channels | array | Channels attached to the collection (see below). |
| --- | --- | --- |
Embedded channel fields:
Embedded channels now return the same fields as the standalone GET /channels endpoint:
| Field | Type | Description |
|---|---|---|
| id | integer | Channel identifier. |
| --- | --- | --- |
| name | string | Display name of the social account. |
| --- | --- | --- |
| type | string | Platform type (see GET /channels for the full list of values). |
| --- | --- | --- |
| status | string | ACTIVE (connected and working) or INACTIVE (disconnected or needs re-auth). |
| --- | --- | --- |
| createdAt | string | ISO 8601 timestamp of when the channel was connected. |
| --- | --- | --- |
| updatedAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
Error example: brand not found:
Request:
curl “https://nuelink.com/api/public/v1/brands/99999999/collections” \
-H “Authorization: Bearer YOUR_API_KEY”
Response: 404 Not Found:
{
“status”: “error”,
“message”: “Brand not found.”,
“errors”: {
“brand”: “Brand not found.”
}
}
GET /brands/{brandId}/channels
Section titled “GET /brands/{brandId}/channels”Lists all channels connected to a brand (both active and inactive).
Path parameters:
| Parameter | Type | Description |
|---|---|---|
| brandId | integer | ID from the brands list. |
| --- | --- | --- |
Query parameters:
| Parameter | Type | Default | Min | Max | Description |
|---|---|---|---|---|---|
| page | integer | 1 | 1 | - | Page number. |
| per_page | integer | 10 | 1 | 100 | Items per page. |
Request:
curl “https://nuelink.com/api/public/v1/brands/13493/channels?per_page=10&page=1” \
-H “Authorization: Bearer YOUR_API_KEY”
Response: 200 OK:
{
“status”: “success”,
“data”: [
{
“id”: 299664,
“name”: “fernwood-signups”,
“status”: “ACTIVE”,
“type”: “Instagram”,
“createdAt”: “2026-04-16T10:32:53.000000Z”,
“updatedAt”: “2026-04-16T10:32:53.000000Z”
},
{
“id”: 60112,
“name”: “fernwoodbotanicals”,
“status”: “INACTIVE”,
“type”: “Instagram”,
“createdAt”: “2023-08-10T11:39:10.000000Z”,
“updatedAt”: “2025-04-06T11:00:17.000000Z”
},
{
“id”: 60111,
“name”: “FernwoodBotanicals”,
“status”: “INACTIVE”,
“type”: “X”,
“createdAt”: “2023-08-10T11:38:53.000000Z”,
“updatedAt”: “2026-01-06T10:55:07.000000Z”
}
],
“pagination”: {
“currentPage”: 1,
“perPage”: 10,
“total”: 8,
“lastPage”: 1,
“nextPageUrl”: null,
“prevPageUrl”: null
}
}
Response fields (per channel):
| Field | Type | Description |
|---|---|---|
| id | integer | Channel identifier. |
| --- | --- | --- |
| name | string | Display name of the connected social account. |
| --- | --- | --- |
| status | string | ACTIVE (connected and working) or INACTIVE (disconnected or needs re-auth). |
| --- | --- | --- |
| type | string | Platform type (see table below). |
| --- | --- | --- |
| createdAt | string | ISO 8601 timestamp of when the channel was connected. |
| --- | --- | --- |
| updatedAt | string | ISO 8601 timestamp. |
| --- | --- | --- |
Supported values for type:
Facebook, Instagram, Threads, LinkedIn, Google Business, YouTube, TikTok, Pinterest, X, Mastodon, Bluesky, Telegram.
POST /brands/{brandId}/media
Section titled “POST /brands/{brandId}/media”Uploads a media file (image or video) and returns an id you can reference when creating a post.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
| brandId | integer | ID from the brands list. |
| --- | --- | --- |
Request body:
multipart/form-data with a single field:
| Field | Type | Description |
|---|---|---|
| media | file | The image or video to upload. |
| --- | --- | --- |
Request:
curl -X POST https://nuelink.com/api/public/v1/brands/13493/media \
-H “Authorization: Bearer YOUR_API_KEY” \
-F “media=@./photo.jpg”
Response: 201 Created:
{
“status”: “success”,
“data”: {
“id”: “bWVkaWEvcmo2dzRhSVY4YUJOMWJoU3c4U2t6VEJOenVXcjRoTmsuanBn”,
“type”: “image/jpeg”,
“size”: 4587311
}
}
Response fields:
| Field | Type | Description |
|---|---|---|
| id | string | Opaque media ID. Pass this as media[].id when posting. |
| --- | --- | --- |
| type | string | MIME type detected by the server. |
| --- | --- | --- |
| size | integer | File size in bytes. |
| --- | --- | --- |
Supported file types and size limits:
| Type | Accepted formats | Max size |
|---|---|---|
| Image | PNG, JPG / JPEG | 100 MB |
| --- | --- | --- |
| Video | MP4, MOV | 100 MB |
| --- | --- | --- |
| Document | PDF (used for LinkedIn document posts) | 100 MB |
| --- | --- | --- |
Nuelink accepts a single file per upload and returns a media id you can reference in POST /posts.
ℹ️ Platform-specific constraints still apply. Even within the 100 MB ceiling, each social platform enforces its own limits: Instagram caps video length, TikTok requires certain aspect ratios, and so on. Media that passes Nuelink’s upload check may still be rejected at publish time by the target platform. Check each platform’s publishing guidelines when targeting a specific format.
Error example: no file sent:
Response: 422 Unprocessable Entity:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“media”: [
“The media field is required.”
]
}
}
Error example: file too large:
Response: 422 Unprocessable Entity:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“media”: [
“The media file may not be greater than 100 megabytes.”
]
}
}
Error example: unsupported format:
Response: 422 Unprocessable Entity:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“media”: [
“The media must be a file of type: png, jpg, jpeg, mp4, mov, pdf.”
]
}
}
POST /brands/{brandId}/collections/{collectionId}/posts
Section titled “POST /brands/{brandId}/collections/{collectionId}/posts”Creates a post in the given collection. The post is published to every channel in the collection, subject to publishMode.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
| brandId | integer | ID from the brands list. |
| --- | --- | --- |
| collectionId | integer | ID from the collections list. |
| --- | --- | --- |
Request body fields:
| Field | Type | Required? | Description |
|---|---|---|---|
| publishMode | string | ✅ | QUEUE, SCHEDULE, IMMEDIATE, or DRAFT. |
| --- | --- | --- | --- |
| caption | string | ✅ (unless poll is set) | Main post text. 1-3000 characters. |
| --- | --- | --- | --- |
| poll | object | ⚠️ (required if no caption) | Poll configuration. See below. |
| --- | --- | --- | --- |
| title | string | No | Optional title (used by YouTube, Pinterest, etc.). Max 255 chars. |
| --- | --- | --- | --- |
| alt | string | No | Alt text for accessibility. Max 255 chars. |
| --- | --- | --- | --- |
| link | string | No | URL to attach to the post. Max 1000 chars. |
| --- | --- | --- | --- |
| scheduledAt | string | ✅ (if publishMode = SCHEDULE) | Future timestamp in format Y-m-d H:i:s (e.g. “2026-10-10 12:12:00”). Interpreted in the brand’s timezone (see timezone field on the brand). |
| --- | --- | --- | --- |
| postAsStory | boolean | No | If true, publish as a Story on channels that support Stories (Instagram and Facebook). Channels on other platforms in the collection will publish the post as a regular feed post. |
| --- | --- | --- | --- |
| comment | object | No | Auto-comment posted after publish. See below. |
| --- | --- | --- | --- |
| media | array | No | Media attachments: use id from /media upload or a direct url. See below. |
| --- | --- | --- | --- |
| platforms | object | No | Platform-specific overrides, see Platform-specific options. |
| --- | --- | --- | --- |
ℹ️ scheduledAt timezone: Timestamps are interpreted in the brand’s timezone, which you can read from the timezone field returned by GET /brands. For example, if the brand’s timezone is America/New_York and you send “2026-10-10 12:12:00”, the post will publish at 12:12 PM Eastern Time.
ℹ️ publishMode values: Nuelink has a built-in Queue System per brand that manages scheduled posts (with pause, resume, and shuffle controls in the dashboard). QUEUE adds the post to that queue using the next available time slot; SCHEDULE places the post at an exact scheduledAt time; IMMEDIATE publishes now; DRAFT saves the post without publishing or scheduling.
poll object:
| Field | Type | Required? | Description |
|---|---|---|---|
| question | string | ✅ | Poll question. Max 280 chars. |
| --- | --- | --- | --- |
| options | array | ✅ | 2-4 option strings. Each option max 30 chars. |
| --- | --- | --- | --- |
| period | string | ✅ | How long the poll is open: 1d, 3d, or 7d. |
| --- | --- | --- | --- |
| correctOptionIndex | integer | No | Zero-based index of the “correct” answer (quiz mode). |
| --- | --- | --- | --- |
| explanation | string | No | Shown after voting. Max 512 chars. |
| --- | --- | --- | --- |
ℹ️ Platforms that support polls: Polls and quizzes are supported on LinkedIn, X, Telegram, Threads, Mastodon, and Bluesky. Channels on other platforms in the collection will ignore the poll (or fall back to a caption-only post).
comment object:
| Field | Type | Required? | Description |
|---|---|---|---|
| comment | string | ✅ | Comment text. Max 1000 chars. |
| --- | --- | --- | --- |
| delay | integer | ✅ | Seconds to wait after publishing before posting the comment. |
| --- | --- | --- | --- |
media array:
Each element must have either id (from /media upload) or url (direct public URL), but not both.
“media”: [
{ “id”: “bWVkaWEvWThQT0NxWWFRcEtIRXZYMW16U0hsZFRnN0hFdEpYVzIubXA0” },
{ “url”: “https://example.com/hero.jpg” }
]
ℹ️ Docs-only clarification: The OpenAPI schema marks both id and url as optional with no mutual-exclusion rule. Documenting the intended behavior here - worth tightening the schema using oneOf.
Example 1: Simple immediate text post
Section titled “Example 1: Simple immediate text post”Request:
curl -X POST https://nuelink.com/api/public/v1/brands/13493/collections/40656/posts \
-H “Authorization: Bearer YOUR_API_KEY” \
-H “Content-Type: application/json” \
-d ’{
“caption”: “New drop alert 🌿 The Rosewater Mist is back in stock - shop now at fernwoodbotanicals.example.com”,
“publishMode”: “IMMEDIATE”
}’
Response: 201 Created:
{
“status”: “success”,
“data”: {
“id”: 4364709,
“message”: “Post created successfully”
}
}
Example 2: Scheduled post with a single image
Section titled “Example 2: Scheduled post with a single image”Request:
curl -X POST https://nuelink.com/api/public/v1/brands/13493/collections/40656/posts \
-H “Authorization: Bearer YOUR_API_KEY” \
-H “Content-Type: application/json” \
-d ’{
“caption”: “Something new is coming to the garden next week 🌱 Stay tuned.”,
“alt”: “Close-up of dew on a fern leaf at sunrise.”,
“publishMode”: “SCHEDULE”,
“scheduledAt”: “2026-10-10 12:12:00”,
“media”: [
{ “id”: “bWVkaWEvcmo2dzRhSVY4YUJOMWJoU3c4U2t6VEJOenVXcjRoTmsuanBn” }
]
}’
Response: 201 Created:
{
“status”: “success”,
“data”: {
“id”: 4364710,
“message”: “Post created successfully”
}
}
Example 3: Poll post
Section titled “Example 3: Poll post”curl -X POST https://nuelink.com/api/public/v1/brands/13493/collections/40656/posts \
-H “Authorization: Bearer YOUR_API_KEY” \
-H “Content-Type: application/json” \
-d ’{
“publishMode”: “IMMEDIATE”,
“poll”: {
“question”: “Which scent should we bring back for summer?”,
“options”: [“Rosewater & Oak”, “Wild Fig”, “Jasmine Honey”],
“period”: “3d”
}
}‘
Example 4: Full example with every option
Section titled “Example 4: Full example with every option”This mirrors the Postman collection’s example. Most real requests will use a small subset of these fields.
Request:
curl -X POST https://nuelink.com/api/public/v1/brands/13493/collections/40656/posts \
-H “Authorization: Bearer YOUR_API_KEY” \
-H “Content-Type: application/json” \
-d ’{
“title”: “Spring Collection Launch”,
“caption”: “Our new Spring Collection is live 🌿 Hand-formulated with botanicals from our own garden. Link in bio to shop.”,
“alt”: “Flat lay of Spring Collection skincare bottles on a bed of fresh herbs.”,
“link”: “https://fernwoodbotanicals.example.com/spring”,
“publishMode”: “SCHEDULE”,
“scheduledAt”: “2026-10-10 12:12:00”,
“postAsStory”: true,
“comment”: {
“comment”: “Tap the link above to see the full collection 🌱”,
“delay”: 30
},
“media”: [
{ “id”: “bWVkaWEvWThQT0NxWWFRcEtIRXZYMW16U0hsZFRnN0hFdEpYVzIubXA0” },
{ “url”: “https://media.nuelink.com/media/bWVkaWEvanNadVY5Rm8xODdMUWJLZ2wzUmRyd2gzTkNRbHdZVE1TdUpR” }
],
“platforms”: {
“instagram”: {
“collab”: “harperandvine”,
“location”: { “id”: “abc123”, “name”: “Brooklyn, NY” },
“trialReel”: “MANUAL”,
“shareToFeed”: true
},
“facebook”: {
“collab”: “harperandvine”,
“location”: { “id”: “abc123”, “name”: “Brooklyn, NY” }
},
“tiktok”: {
“sendToInbox”: true
},
“youtube”: {
“tags”: [“skincare”, “botanicals”, “spring-collection”],
“playlists”: [
{ “channelId”: 60107, “playlistIds”: [“PL123”, “PL456”] }
]
}
}
}‘
Error examples
Section titled “Error examples”Missing caption and no poll - 422:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“caption”: [
“The caption field is required when poll is not present.”
]
}
}
Invalid scheduledAt (past date) - 422:
{
“status”: “error”,
“message”: “The given data was invalid.”,
“errors”: {
“scheduledAt”: [
“The scheduled at must be a future date.”
]
}
}
Unknown YouTube channel referenced in playlists - 404:
{
“status”: “error”,
“message”: “Channel not found or does not belong to the brand”,
“errors”: {
“platforms.youtube.playlists.0.channelId”: “Channel not found or does not belong to the brand”
}
}
Collection doesn’t exist - 404:
{
“status”: “error”,
“message”: “Collection not found.”,
“errors”: {
“collection”: “Collection not found.”
}
}
Platform-specific options
Section titled “Platform-specific options”The platforms object on POST /posts lets you send per-platform overrides. Only include keys for platforms you want to customize - omitted platforms use sensible defaults.
platforms.instagram
Section titled “platforms.instagram”Instagram-specific overrides. The collab field drives Nuelink’s Co-authoring feature, which tags a partner account as co-author of the post.
| Field | Type | Description |
|---|---|---|
| collab | string | Username of the Instagram partner account to invite as a co-author (Instagram Collab). Max 1000 chars. |
| --- | --- | --- |
| location | object | Geotag. Requires id (Instagram’s location ID) and name (display name). |
| --- | --- | --- |
| trialReel | string | Enables Instagram’s Trial Reel feature, which shows the Reel to non-followers first to test performance. Value controls what happens after the 72-hour trial window: MANUAL requires the user to manually share the Reel with their followers from Instagram; SS_PERFORMANCE auto-shares it with followers if it performs well during the trial. |
| --- | --- | --- |
| shareToFeed | boolean | If true, Reels are also shared to the main feed. |
| --- | --- | --- |
ℹ️ Trial Reel is Instagram-only and video-only. It only applies when the post is a Reel. Setting trialReel on image posts or on non-Instagram channels has no effect.
platforms.facebook
Section titled “platforms.facebook”Facebook-specific overrides. Mirrors the Instagram Collab behavior for Facebook co-authoring.
| Field | Type | Description |
|---|---|---|
| location | object | Geotag with id and name. |
| --- | --- | --- |
platforms.tiktok
Section titled “platforms.tiktok”| Field | Type | Description |
|---|---|---|
| sendToInbox | boolean | If true, the scheduled video is delivered to the TikTok app inbox as a notification instead of being auto-published. The user opens the notification on their phone to edit, add music, and publish from the TikTok app. Use this when you want Nuelink to handle scheduling but let the user finalize the post manually in TikTok’s native editor. |
| --- | --- | --- |
platforms.youtube
Section titled “platforms.youtube”| Field | Type | Description |
|---|---|---|
| tags | array | Video tags. Each tag max 255 chars. |
| --- | --- | --- |
| playlists | array | Playlists to add the video to. Each item is { channelId: integer, playlistIds: [string] }. |
| --- | --- | --- |
Changelog
Section titled “Changelog”1.0.0-alpha - 2026-04-21
Section titled “1.0.0-alpha - 2026-04-21”- Initial alpha release.
- Endpoints: GET /me, GET /brands, GET /brands/{id}/collections, GET /brands/{id}/channels, POST /brands/{id}/media, POST /brands/{id}/collections/{id}/posts.
Known gaps in this alpha
Section titled “Known gaps in this alpha”The following are explicitly not available in the alpha and are planned for later releases:
- Listing, updating, or deleting posts (GET /posts, PATCH /posts/{id}, DELETE /posts/{id}).
- Retrieving analytics or post performance.
- Webhooks for publish success / failure events.
- Targeting a subset of channels within a collection (today, posts go to all channels in the collection).
- Listing or deleting previously uploaded media.
If any of these are blockers for your integration, let us know and we’ll prioritize accordingly.
Feedback
Section titled “Feedback”This is alpha software: we want your feedback. Email support@nuelink.com (or whichever channel Nuelink prefers) with bugs, questions, and feature requests.