User Profiles API
These endpoints allow you to integrate Upload-Post directly into your platform by managing user profiles and generating secure tokens for account linking.
See the User Profile Integration Guide for a conceptual overview and workflow.
Authentication
All API requests require authentication using your API Key. Include it in the Authorization header for every request:
Authorization: Apikey YOUR_API_KEY
Replace YOUR_API_KEY with the actual API key provided to you.
User Profile Management
Manage user profiles within Upload-Post that correspond to users on your platform.
Endpoint
/api/uploadposts/users
Create User Profile
Creates a new profile linked to a user on your platform.
-
Method:
POST -
Headers:
Authorization: Apikey YOUR_API_KEYContent-Type: application/json
-
Body Parameters:
Name Type Required Description username String Yes A unique identifier for the user on your platform (e.g., your internal ID). -
Example Body:
{
"username": "your_platform_user_id_123"
} -
Success Response (201 Created):
{
"profile": {
"created_at": "Fri, 02 May 2025 21:43:14 GMT",
"social_accounts": {
"tiktok": ""
// Other platforms will appear here as they are connected
},
"username": "your_platform_user_id_123"
},
"success": true
}profile: Contains details of the newly created profile.created_at: Timestamp of profile creation.social_accounts: Object showing connected accounts (initially empty or with placeholders).username: The unique identifier provided.
success: Indicates successful creation.
-
Error Responses:
400 Bad Request: Missing or invalidusername.401 Unauthorized: Invalid or missing API Key.403 Forbidden: Profile limit reached for the current plan (error_code: PROFILE_LIMIT_REACHED).409 Conflict: A profile with the providedusernamealready exists.
Get User Profiles
Retrieves a list of all user profiles created under your API key.
- Method:
GET - Headers:
Authorization: Apikey YOUR_API_KEY
- Query Parameters: None
- Success Response (200 OK):
{
"limit": 10,
"plan": "default",
"profiles": [
{
"created_at": "2025-04-02T17:44:33.229755",
"social_accounts": {
"facebook": {
"display_name": "FB User",
"social_images": "url_to_fb_image"
},
"instagram": {
"display_name": "IG User",
"social_images": "url_to_ig_image"
}
// ... other connected platforms with their details
},
"username": "your_platform_user_id_1"
},
{
"created_at": "Fri, 02 May 2025 21:43:14 GMT",
"social_accounts": {
"tiktok": "" // Example of a platform added but not yet connected
},
"username": "your_platform_user_id_2"
}
],
"success": true
}limit: The maximum number of profiles allowed by the current plan.plan: The subscription plan associated with the API key.profiles: An array of user profile objects.created_at: Timestamp of profile creation.social_accounts: An object detailing connected social media accounts. Each key is the platform name (e.g.,facebook,instagram,tiktok). The value can be an object with details (username,handle,display_name,social_images) or an empty string/null if not fully connected. See Identifying a connected account.capabilities: On the accounts that expose it (today,tiktok), the list of optional features that this particular connection supports. See Account capabilities.reauth_required:truewhen the connection's token has expired and the account must be reconnected before it can publish.username: The unique identifier for the profile.
success: Indicates successful retrieval.
- Error Responses:
401 Unauthorized: Invalid or missing API Key.
Identifying a connected account
Every connected account in social_accounts carries two different strings, and
they answer two different questions:
| Field | What it is | Use it to |
|---|---|---|
username | The account identifier: the platform's own id for the account, and the key this connection is stored under. Opaque by design. | Bind your own records to a destination. |
handle | The public @name on the platform. | Show a human which account a post goes to. |
display_name | The profile's display name. | Labels in your UI. |
For most platforms the identifier is a numeric id (Instagram's IG user id,
Facebook's page id, YouTube's channel id). For TikTok it is the account's
open_id, which looks like -000g91dgwXgwtNwc-V8dolLsQJMUY3MQAfv. That is the
identifier TikTok itself issues; it is scoped to the application, so the
same TikTok account has a different open_id in every app that connects to it,
including ours. Within Upload-Post it is stable:
- it does not change when the user reconnects the same account,
- it does not change when the user renames the account or changes its
@handle —
handlefollows the rename on the next reconnection,usernamenever moves, - it does differ if a different TikTok account is connected. A changed
usernametherefore means "this is another account", which is exactly the signal you want before publishing.
GET /api/uploadposts/users (all profiles) and
GET /api/uploadposts/users/{profile} (one profile) are the supported
read-only endpoints. Both return the same social_accounts shape, and neither
publishes or modifies anything.
Account capabilities
Not every connected account supports the same optional fields. A connected
account can report what it supports in a capabilities array, so you can check
it before sending optional fields instead of guessing:
{
"success": true,
"profiles": [
{
"username": "your_profile",
"social_accounts": {
"tiktok": {
"display_name": "Fotoexamen",
"handle": "fotoexamen",
"social_images": "https://storage.googleapis.com/.../avatar.jpg",
"reauth_required": false,
"capabilities": [
"music",
"location",
"cover_image",
"cover_timestamp",
"draft",
"video_privacy",
"photo_privacy",
"profile_analytics",
"comments",
"trend_search"
]
}
}
}
]
}
TikTok capability values
| Capability | What it unlocks |
|---|---|
music | Attach a Commercial Music Library track: tiktok_music_id, tiktok_music_volume, tiktok_music_start, tiktok_music_end, tiktok_original_sound_volume. See Get TikTok Trending Music. |
location | Tag a place on the post: tiktok_location_id + tiktok_location_name. See Get TikTok Locations. |
cover_image | Set a custom cover image: tiktok_cover_image_url / tiktok_cover_image. |
cover_timestamp | Pick the cover frame with cover_timestamp (milliseconds). |
draft | Send the video to the account's drafts with post_mode=MEDIA_UPLOAD (alias tiktok_upload_to_draft=true). |
photo_privacy | The connection accepts privacy_level on photo posts. |
video_privacy | The connection accepts privacy_level on video posts. Connections without it always publish videos as public — use draft instead. |
inbox_fallback | When TikTok's daily active-user cap is hit, the post is delivered to the account's TikTok inbox as a draft instead of failing. See Reached Active User Cap. |
comments | Everything in the Comments API with platform=tiktok: list, create, reply, delete, the replies under a comment (comment_id) and the hide / like / pin verbs — plus attaching a first_comment to a post. Only granted on reconnection — see below. |
trend_search | GET /suggestions?type=keywords: the terms people search around a word. Only granted on reconnection — see below. |
profile_analytics | Profile-level TikTok analytics in Get Analytics — including the full per-post breakdown (retention, impression sources, audience types) — plus Audience Insights with its category benchmark, and GET /suggestions?type=hashtags. Granted by any recent connection; no reconnection needed. |
video_privacy and inbox_fallback on one side and music / location /
cover_image / draft / profile_analytics on the other are
mutually exclusive: a connection reports one group or the other, never both.
Neither group changes how you ask for a draft. post_mode=MEDIA_UPLOAD (and its
alias tiktok_upload_to_draft=true) works on every TikTok connection, whether it
reports draft or inbox_fallback — do not branch on the capability list.
inbox_fallback is not a mode you can request: it is what happens on its own
when TikTok's daily active-user cap is hit.
comments and trend_search only appear after a reconnectionEvery other capability depends on how the account was connected. These two
depend on a permission TikTok grants at the moment of connecting, and it
only started issuing it recently. An account connected before that publishes
exactly as it always did — nothing about its uploads changes — but it will not
report comments or trend_search, and the endpoints that need them answer
400 with error_code: "tiktok_reconnect_required".
The fix is the same one-click reconnection as any other missing capability: the account owner reconnects TikTok from Manage Users, or you send them through a white-label connect link. Nothing in your integration changes.
This is also why first_comment on TikTok can come back as a warning instead of
a comment: without comments the post is still published, and the response says
the first comment was skipped.
Sending a field your connection does not support is safe. The post is still
published: the upload response comes back with a warnings array of plain
strings naming the field that was ignored and telling you that reconnecting the
TikTok account enables it.
{
"success": true,
"warnings": [
"tiktok_music_id ignored: your current TikTok connection cannot attach a track from TikTok's music catalogue. Reconnect your TikTok account from Manage Users (https://app.upload-post.com/manage-users) to enable it."
]
}
To enable a missing capability, reconnect the account from Manage Users (or send your end user through a white-label connect link). Nothing in your integration changes — same endpoints, same field names.
capabilities as an open listNew values can be added over time. Check whether the capability you need is present rather than matching the array exactly, and fall back gracefully when a capability is missing.
Get a Specific User Profile
Retrieves information for a single user profile using its username.
- Method:
GET - Endpoint:
/api/uploadposts/users/{username}
Path Parameters
| Parameter | Type | Description |
|---|---|---|
username | string | Required. The username of the profile to retrieve. |
Success Response (200 OK)
If the profile is found, the API will return a JSON object with the profile details.
{
"success": true,
"profile": {
"created_at": "2023-10-27T10:00:00Z",
"social_accounts": {
"tiktok": {
"username": "tiktok_user_123",
"display_name": "User Display Name",
"social_images": "https://example.com/image.jpg"
},
"bluesky": {
"username": "user.bsky.social",
"display_name": "Bluesky User",
"social_images": "https://example.com/avatar.jpg"
},
"instagram": null
},
"username": "specific_profile_name"
}
}
Error Response (404 Not Found)
If no profile is found with the specified username, the API will return:
{
"success": false,
"message": "Profile not found"
}
Delete User Profile
Deletes an existing user profile and its associated data (like social connections).
-
Method:
DELETE -
Headers:
Authorization: Apikey YOUR_API_KEYContent-Type: application/json
-
Body Parameters:
Name Type Required Description username String Yes The unique identifier of the profile to delete. -
Example Body:
{
"username": "user_id_to_delete"
} -
Success Response (200 OK):
{
"message": "Perfil eliminado correctamente",
"success": true
} -
Error Responses:
400 Bad Request: Missing or invalidusername.401 Unauthorized: Invalid or missing API Key.404 Not Found: No profile found with the providedusername.
JWT Management
Generate and validate JWTs for the secure social account linking process.
Endpoint: Generate JWT URL
/api/uploadposts/users/generate-jwt
Generates a secure, single-use URL containing a JWT. Your user visits this URL to link their social media accounts.
-
Method:
POST -
Headers:
Authorization: Apikey YOUR_API_KEYContent-Type: application/json
-
Body Parameters:
Name Type Required Description username String Yes The identifier for the user profile for which the JWT is being generated. redirect_url String No (Optional) The URL to which the user will be redirected after linking their social account. logo_image String No (Optional) A URL to a logo image to display on the linking page for branding purposes. redirect_button_text String No (Optional) The text to display on the redirect button after linking. Defaults to "Logout connection". connect_title String No (Optional) Custom title text for the connection page. Defaults to "Connect Social Media Accounts". connect_description String No (Optional) Custom description text for the connection page. Defaults to "Connect your social media accounts to manage your posts.". platforms Array No (Optional) List of platforms to show for connection. Possible values: 'tiktok', 'instagram', 'linkedin', 'youtube', 'facebook', 'x', 'threads', 'google_business'. Defaults to all supported platforms. show_calendar Boolean No (Optional) Whether to show the calendar view on the connection page. Defaults to true.readonly_calendar Boolean No (Optional) When true, shows only a read-only calendar view. The user cannot edit, delete, or create posts, and cannot connect or disconnect social accounts. Ideal for sharing a content calendar with end clients. Defaults tofalse.language String No (Optional) Forces the language of the connection page for this profile. Supported values: en,es,de,fr,pt,pl,tr. When omitted, the page automatically detects the visitor's browser language and falls back to English.ui_labels Object No (Optional) Flat object of i18n key → replacement string, to override individual pieces of connect-page UI text. See Custom UI Labels. -
Supported languages:
Value Language enEnglish esSpanish deGerman frFrench ptPortuguese plPolish trTurkish -
Example Body:
{
"username": "your_platform_user_id_123"
} -
Success Response (200 OK):
{
"access_url": "https://app.upload-post.com/connect?token=GENERATED_JWT_TOKEN",
"success": true,
"duration": "48h"
}access_url: The secure URL your user needs to visit. Redirect your user to this URL.success: Alwaystrueif the request was successful.duration: The validity period of the generated JWT (48 hours).
-
Example Request (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/generate-jwt \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "your_platform_user_id_123"}' -
Example Request with Calendar Disabled (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/generate-jwt \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "your_platform_user_id_123", "show_calendar": false}' -
Example Request with Read-Only Calendar for Clients (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/generate-jwt \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "your_platform_user_id_123", "readonly_calendar": true, "logo_image": "https://youragency.com/logo.png", "connect_title": "Your Content Calendar"}'This generates a link where the client only sees the calendar with scheduled posts (social channel, date/time, visual, text) but cannot edit anything or access other sections.
-
Example Success Response (200 OK):
{
"access_url": "https://app.upload-post.com/connect?token=GENERATED_JWT_TOKEN_STRING",
"duration": "48h",
"success": true
} -
Calendar Deep Link: If you want users to land directly on the shared calendar view, replace the path with
/connect/calendarwhile keeping the token intact, e.g.https://app.upload-post.com/connect/calendar?token=GENERATED_JWT_TOKEN. The page will automatically fall back to/connectwhen the profile hasshow_calendardisabled. Whenreadonly_calendaristrue, the user is automatically redirected to the calendar view regardless of the URL path. -
Error Responses:
400 Bad Request: Missing or invalidusername, or an invalidui_labelspayload (see Custom UI Labels).401 Unauthorized: Invalid or missing API Key.403 Forbidden: Profile exists but is blocked by plan limits (error_code: PROFILE_BLOCKED).404 Not Found: No profile found with the providedusername(error_code: PROFILE_NOT_FOUND).
-
Integration tip: If JWT generation returns
404, callGET /api/uploadposts/usersfirst to confirm the profile exists and that profile creation did not fail due to plan limits.
Custom UI Labels
connect_title, connect_description and redirect_button_text cover the three most visible strings on the connection page. ui_labels goes further: it lets a white-label integration override any individual piece of connect-page UI text, in any language, without waiting for a translation to ship.
ui_labels is a flat object mapping the connect page's own i18n dot-path keys to the replacement strings:
{
"username": "your_platform_user_id_123",
"language": "tr",
"ui_labels": {
"connect.connectButton": "Bağlan",
"connect.notConnected": "Bağlı değil"
}
}
- Example Request (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/generate-jwt \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"username": "your_platform_user_id_123",
"language": "tr",
"ui_labels": {
"connect.connectButton": "Bağlan",
"connect.notConnected": "Bağlı değil"
}
}'
Validation rules
| Rule | Limit |
|---|---|
| Maximum number of entries | 100 |
| Key format | Must match ^[a-zA-Z0-9_.]+$ — letters, numbers, dots and underscores only |
| Value type | Must be a string |
| Value length | Maximum 300 characters |
A violation returns 400 Bad Request with the specific reason:
{
"success": false,
"message": "Invalid ui_labels key 'bad key!'. Keys may only contain letters, numbers, dots and underscores"
}
{
"success": false,
"message": "ui_labels['connect.x'] must be a string"
}
Update semantics
ui_labels is stored on the profile and persists across JWT generations:
| You send | Result |
|---|---|
| The field is omitted | Previously stored labels are left untouched |
"ui_labels": { ... } (non-empty) | Replaces the stored labels with what you sent |
"ui_labels": null | Clears all stored labels |
"ui_labels": {} | Clears all stored labels |
The stored labels are returned inside the profile object by GET /api/uploadposts/users/validate-jwt, so you can read back what is currently applied.
The keys are the connect page's own translation keys, not free-form identifiers. An unknown key is simply never rendered. Override only keys you have confirmed exist — verify each one against the live connect page after setting it.
Mobile OAuth Compatibility
When users open the connection page on a mobile device (iOS or Android), the
operating system may intercept OAuth URLs (e.g. instagram.com, accounts.google.com)
and open the corresponding native app instead of keeping the flow in the browser.
Because native apps cannot handle the OAuth authorization URL, the connection fails.
Upload-Post automatically detects mobile browsers and routes OAuth redirects through
a secure intermediate page (/api/uploadposts/oauth/bounce) that performs the
redirect via JavaScript. This bypasses Universal Links (iOS) and App Links (Android)
interception so the OAuth flow stays entirely in the mobile browser.
No action is required from API consumers — the mobile-safe redirect is applied
automatically when the user accesses the access_url on a mobile device.
Endpoint: Validate JWT
/api/uploadposts/users/validate-jwt
(Optional) Allows you to validate a JWT token. The primary validation occurs automatically when the user accesses the access_url.
- Method:
GET - Headers:
Authorization: Bearer YOUR_JWT_TOKEN
- Body Parameters: None. The token is read from the
Authorizationheader, not from the request body. - Example Request (curl):
# Replace YOUR_JWT_TOKEN with the actual token string
curl -X GET https://api.upload-post.com/api/uploadposts/users/validate-jwt \
-H "Authorization: Bearer YOUR_JWT_TOKEN" - Success Response (200 OK - Valid Token): Returns the profile details associated with the token.
{
"profile": {
"social_accounts": {
"tiktok": null,
"instagram": "connected_account_details",
// ... other platforms
},
"username": "your_platform_user_id_123",
"ui_labels": {
"connect.connectButton": "Bağlan",
"connect.notConnected": "Bağlı değil"
}
},
"success": true
}profile: Contains details about the user profile linked to the token.social_accounts: An object showing the connection status for various platforms (e.g.,nullif not connected, or details if connected).username: The unique identifier provided when the profile was created.ui_labels: The connect-page text overrides currently stored for this profile, as set viagenerate-jwt. Empty or absent when no overrides are stored.
success: Indicates the token is valid.
- Success Response (200 OK - Invalid Token):
{
"isValid": false,
"reason": "Token expired or invalid signature" // Example reason
} - Error Responses:
401 Unauthorized: Invalid, expired, or missing JWT token in theAuthorizationheader.
Facebook Pages
Retrieve Facebook page IDs associated with user profiles to enable posting to Facebook pages.
Endpoint
/api/uploadposts/facebook/pages
Get Facebook Pages
Fetches Facebook page IDs associated with a profile. You can use this endpoint to connect and start posting on Facebook pages.
-
Method:
GET -
Headers:
Authorization: Apikey YOUR_API_KEY
-
Query Parameters:
Name Type Required Description profile String No The unique identifier of the profile. If not specified, returns all pages for your account. -
Example Request (curl):
curl 'https://api.upload-post.com/api/uploadposts/facebook/pages?profile=your_profile' \
-H 'Authorization: Apikey YOUR_API_KEY' -
Example Request (without profile parameter):
curl 'https://api.upload-post.com/api/uploadposts/facebook/pages' \
-H 'Authorization: Apikey YOUR_API_KEY' -
Success Response (200 OK):
{
"pages": [
{
"page_id": "123456789",
"page_name": "My Business Page",
"profile": "your_platform_user_id_123"
},
{
"page_id": "987654321",
"page_name": "Another Page",
"profile": "your_platform_user_id_123"
}
],
"success": true
}pages: Array of Facebook page objects associated with the profile(s).page_id: The Facebook page ID that can be used for posting.page_name: The display name of the Facebook page.profile: The profile identifier associated with this page.
success: Indicates successful retrieval.
-
Error Responses:
401 Unauthorized: Invalid or missing API Key.404 Not Found: No profile found with the provided identifier (if profile parameter is specified).
Manual Credential Connections
Most platforms are connected through the JWT connection page (OAuth). A few platforms instead use a manual credential model: you submit the credentials directly to a dedicated endpoint, Upload-Post validates them against the platform, encrypts the secret at rest, and links it to the profile. There is no OAuth flow and no browser redirect. Tokens/webhooks do not expire, so no reconnection is required unless you revoke them.
Connect Discord
Links a Discord channel incoming webhook to a profile. Upload-Post validates the webhook by performing a GET on the webhook URL (expecting a 200 with the webhook id, channel_id, and guild_id), then encrypts and stores the webhook URL.
-
Method:
POST -
Endpoint:
/api/uploadposts/users/discord/credentials -
Headers:
Authorization: Apikey YOUR_API_KEYContent-Type: application/json
-
Body Parameters:
Name Type Required Description profile_username String Yes The profile to link the Discord webhook to. webhook_url String Yes The Discord channel incoming webhook URL ( https://discord.com/api/webhooks/...).name String No Optional display name for the connection. Defaults to the webhook's name. -
Example Request (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/discord/credentials \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_username": "your_platform_user_id_123",
"webhook_url": "https://discord.com/api/webhooks/123456789/abcdef...",
"name": "My Server #announcements"
}' -
Success Response (200 OK):
{
"success": true,
"message": "Discord credentials saved successfully"
}On success, the profile's
social_accounts.discordis set to the connection key (the suppliedname, or the webhook id whennameis omitted). -
Error Responses:
400 Bad Request: Missingprofile_username/webhook_url, an invalid webhook URL format, or a webhook that fails validation (deleted/invalid).401 Unauthorized: Invalid or missing API Key.404 Not Found: User or profile not found.
To find the webhook URL: open Discord → Server Settings → Integrations → Webhooks → New Webhook, pick the target channel, and click Copy Webhook URL.
Connect Telegram
Links a Telegram bot (bring-your-own bot) and a target chat/channel to a profile. Upload-Post validates the bot token via getMe and the chat via getChat (the bot must be an admin of the chat), then encrypts and stores the bot token.
-
Method:
POST -
Endpoint:
/api/uploadposts/users/telegram/credentials -
Headers:
Authorization: Apikey YOUR_API_KEYContent-Type: application/json
-
Body Parameters:
Name Type Required Description profile_username String Yes The profile to link the Telegram bot to. bot_token String Yes The bot token from @BotFather (e.g. 123456:ABC-DEF...).chat_id String Yes The target chat: a public channel @username, or a numeric chat id (e.g.-100123456789).name String No Optional display name for the connection. Defaults to the bot's username. -
Example Request (curl):
curl -X POST https://api.upload-post.com/api/uploadposts/users/telegram/credentials \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_username": "your_platform_user_id_123",
"bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chat_id": "@my_channel",
"name": "My Channel"
}' -
Success Response (200 OK):
{
"success": true,
"message": "Telegram credentials saved successfully"
}On success, the profile's
social_accounts.telegramis set to the connection key (the suppliedname, or the bot username whennameis omitted). -
Error Responses:
400 Bad Request: Missingprofile_username/bot_token/chat_id, an invalid bot token, or a chat that is not reachable (often because the bot is not an admin of the chat).401 Unauthorized: Invalid or missing API Key.404 Not Found: User or profile not found.
To set up the bot: message @BotFather → /newbot to get a bot_token, then add the bot to your target channel/group as an administrator so it can post.